public double FitEllipse()
    {
        List <Vector2> points = IExtension.GetMaskPoints(img);

        foreach (Vector2 p in points)
        {
            mean += p;
        }

        mean /= points.Count;
        float W = points.Count;


        float[,] C = new float[2, 2];
        foreach (Vector2 point in points)
        {
            C[0, 0] += (point.x - mean.x) * (point.x - mean.x);
            C[0, 1] += (point.x - mean.x) * (point.y - mean.y);

            C[1, 0] += (point.y - mean.y) * (point.x - mean.x);
            C[1, 1] += (point.y - mean.y) * (point.y - mean.y);
        }

        C[0, 0] /= W;
        C[0, 1] /= W;
        C[1, 0] /= W;
        C[1, 1] /= W;

        Matrix2d CM  = new Matrix2d(C);
        SVD      svd = new SVD(C);
        //svd.w - eigenvalue, start from 1
        //svd.u - eigenvector, start from 1

        int max = 1, min = 2;

        if (svd.w[max] < svd.w[min])
        {
            int temp = max;
            max = min;
            min = temp;
        }

        float   major     = 2 * Mathf.Sqrt(svd.w[max]);
        Vector2 majoraxis = new Vector2(svd.u[1, max], svd.u[2, max]);

        majoraxis.Normalize();
        float   minor     = 2 * Mathf.Sqrt(svd.w[min]);
        Vector2 minoraxis = new Vector2(svd.u[1, min], svd.u[2, min]);

        minoraxis.Normalize();

        majorendp   = mean + majoraxis * major;
        majorstartp = mean - majoraxis * major;
        minorendp   = mean + minoraxis * minor;
        minorstartp = mean - minoraxis * minor;

        double error = Mathf.Abs(W - 4 * Mathf.PI * Mathf.Sqrt(CM.Det()));

        error /= W;
        if (!m_engine.m_is_quiet)
        {
            Debug.Log("Like a ellipse error: " + error);                       //10^-2 may be a good threshold
        }
        return(error);
    }
    private List <Vector2> ApproximateCurveAxis(List <Line2> skel_lines)
    {
        Image <Rgb, byte> curveaxis_img = body_img.Copy().Convert <Rgb, byte>();
        List <Vector2>    skel_points   = new List <Vector2>();

        //Image<Gray, byte> temp = face_img[0].Copy();
        //var element = CvInvoke.GetStructuringElement(ElementShape.Cross, new Size(3, 3), new Point(-1, -1));
        //CvInvoke.Dilate(temp, temp, element, new Point(-1, -1), 10, BorderType.Reflect, default(MCvScalar));
        //CvInvoke.Subtract(ori_prune_img, temp, ori_prune_img);
        skel_points = IExtension.GetMaskPoints(ori_prune_img);
        for (int i = 0; i < skel_points.Count; i++)
        {
            if (ori_prune_img[(int)skel_points[i].y, (int)skel_points[i].x].Equals(new Gray(128)))
            {
                this.end_points.Add(i);
            }
        }

        List <Vector2> path_points    = new List <Vector2>();
        List <Vector2> top_mask_point = IExtension.GetMaskPoints(this.face_img[0]);
        Vector2        top_center     = Utility.Average(top_mask_point);
        //double guess_radius = Math.Sqrt(top_mask_point.Count / Math.PI);

        Vector2        start_point     = top_center;
        List <Vector2> attach_boundary = new List <Vector2>();

        if (noface)
        {
            // Vector2 attach_center = Utility.Average(IExtension.GetBoundary(this.attach_img));
            // start_point = attach_center;
            attach_boundary = IExtension.GetBoundary(this.attach_img);
        }

        // closest to top face center
        int    startidx         = 0;
        double mindis_topcenter = double.MaxValue;

        for (int i = 0; i < end_points.Count; i++)
        {
            double dis = double.MaxValue;
            if (noface)
            {
                dis = Utility.DistancePoint2Set(skel_points[end_points[i]], attach_boundary);
            }
            else
            {
                dis = Vector2.Distance(skel_points[end_points[i]], start_point);
            }
            if (dis < mindis_topcenter)
            {
                mindis_topcenter = dis;
                startidx         = end_points[i];
            }
        }

        path_points = FindLongestPath(skel_points, startidx);

        double diss = Vector2.Distance(path_points.First(), start_point);
        double dise = Vector2.Distance(path_points.Last(), start_point);

        if (dise < diss)
        {
            path_points.Reverse();
        }

        path_points = IExtension.ResetPath(path_points, 20);
        // add first point
        if (!noface)
        {
            path_points.Insert(0, top_center);

            // extend ending
            Line2 endray = new Line2(path_points[path_points.Count - 2], path_points[path_points.Count - 1]);
            path_points.Add(endray.GetPointwithT((float)endray.Length() * 10));

            //reset path to 3
            path_points = IExtension.ResetPath(path_points, 3);
        }
        else
        {
            path_points = IExtension.ResetPath(path_points, 3);
            path_points.RemoveRange(0, (int)(0.05 * path_points.Count));
            //path_points.Insert(0, top_center);
        }

        #region visualize
        curveaxis_img = body_img.Copy().Convert <Rgb, byte>();
        for (int i = 0; i < path_points.Count; i++)
        {
            curveaxis_img.Draw(new CircleF(new PointF(path_points[i].x, path_points[i].y), 2.0f), new Rgb(255, 0, i * 2), 1);
        }
        curveaxis_img.Draw(new CircleF(new PointF(path_points[0].x, path_points[0].y), 2.0f), new Rgb(0, 255, 0), 1);
        curveaxis_img.Save(index_forname.ToString() + this.label_forname.ToString() + "_axis_curve.png");
        #endregion

        return(path_points);
    }
    private Line2 ApproximateFromTop()
    {
        //guess axis with top face(s)

        Line2 main_axis = null;

        face_center = new List <Vector2>();
        List <Vector2> normal = new List <Vector2>();

        List <double> minoraxislength = new List <double>();

        foreach (Image <Gray, byte> fimg in face_img)
        {
            Vector2        mean   = new Vector2();
            List <Vector2> points = IExtension.GetMaskPoints(fimg);
            foreach (Vector2 p in points)
            {
                mean += p;
            }
            mean /= points.Count;
            face_center.Add(mean);
            float W = points.Count;

            float[,] C = new float[2, 2];
            foreach (Vector2 point in points)
            {
                C[0, 0] += (point.x - mean.x) * (point.x - mean.x);
                C[0, 1] += (point.x - mean.x) * (point.y - mean.y);

                C[1, 0] += (point.y - mean.y) * (point.x - mean.x);
                C[1, 1] += (point.y - mean.y) * (point.y - mean.y);
            }

            C[0, 0] /= W;
            C[0, 1] /= W;
            C[1, 0] /= W;
            C[1, 1] /= W;

            Matrix2d             CM  = new Matrix2d(C);
            NumericalRecipes.SVD svd = new NumericalRecipes.SVD(C);
            //svd.w - eigenvalue, start from 1
            //svd.u - eigenvector, start from 1

            int max = 1, min = 2;
            if (svd.w[max] < svd.w[min])
            {
                int temp = max;
                max = min;
                min = temp;
            }

            float   major     = 2 * Mathf.Sqrt(svd.w[max]);
            Vector2 majoraxis = new Vector2(svd.u[1, max], svd.u[2, max]);
            majoraxis.Normalize();
            float   minor     = 2 * Mathf.Sqrt(svd.w[min]);
            Vector2 minoraxis = new Vector2(svd.u[1, min], svd.u[2, min]);
            minoraxis.Normalize();

            Vector2 majorendp   = mean + majoraxis * major;
            Vector2 majorstartp = mean - majoraxis * major;
            Vector2 minorendp   = mean + minoraxis * minor;
            Vector2 minorstartp = mean - minoraxis * minor;


            //minoraxislength.Add(Vector2.Distance(minorendp, minorstartp));
            minoraxislength.Add((double)minor * 2);
            normal.Add(Utility.PerpendicularRight(majoraxis));
            //normal.Add(majoraxis);
        }


        if (face_center.Count > 1)
        {
            //Vector2 mean_normal = normal.First();
            Line2  best_top_normal_line = new Line2(face_center.First(), normal.First(), true);
            double maxdis         = 0;
            int    farthesttopidx = 0;
            for (int i = 1; i < face_center.Count; i++)
            {
                double tofirstcenterdis = Vector2.Distance(face_center[0], face_center[i]);
                if (tofirstcenterdis > maxdis)
                {
                    maxdis         = tofirstcenterdis;
                    farthesttopidx = i;
                }
                double dis = best_top_normal_line.DistanceToLine(face_center[i]);
                if (dis > 10)
                {
                    main_axis = null;
                }
            }
            main_axis = new Line2(face_center.First(), face_center[farthesttopidx]);
        }
        else
        {
            Line2   top_normal_line = new Line2(face_center.First(), normal.First(), true);
            Vector2 online_point    = top_normal_line.GetPointwithT((float)minoraxislength[0]);
            if (online_point.x >= 0 && online_point.y >= 0 && online_point.x < body_img.Width && online_point.y < body_img.Height)
            {
                if (!this.body_img[(int)online_point.y, (int)online_point.x].Equals(new Gray(255)))
                {
                    top_normal_line.Flip();
                }
            }
            else
            {
                top_normal_line.Flip();
            }
            main_axis = top_normal_line;
        }

        return(main_axis);
    }
    private List <Vector2> ApproximateStraightAxis(List <Line2> skel_lines)
    {
        // from skeleton lines(aka. thining image lines) (find lines that have the same direction with guessed axis <- when top face is known)
        // ransac, fit one line as main axis
        // update end points
        // set top circle center as start point
        // extend end point

        Line2             new_main_axis = null;
        Image <Rgb, byte> mainaxis_img  = body_img.Copy().Convert <Rgb, byte>();

        Line2 main_axis = null;

        if (iscube)
        {
            main_axis = ApproximateFromCubeTop();
        }
        else
        {
            main_axis = ApproximateFromTop();
        }

        if (main_axis == null)
        {
            return(null);
        }

        if (noface)
        {
            NumericalRecipes.RansacLine2d rcl = new NumericalRecipes.RansacLine2d();
            List <Vector2> thin_points        = IExtension.GetMaskPoints(this.ori_thin_img);
            List <Vector2> linepoints         = new List <Vector2>();
            for (int i = 0; i < skel_lines.Count; i++)
            {
                linepoints.AddRange(skel_lines[i].SamplePoints());
            }
            //Line2 bestline = rcl.Estimate(linepoints);
            Line2  bestline = rcl.Estimate(thin_points);
            double diss     = Vector2.Distance(bestline.start, face_center[0]);
            double dise     = Vector2.Distance(bestline.end, face_center[0]);
            if (dise < diss)
            {
                bestline.Flip();
            }
            new_main_axis = bestline;
        }
        else
        {
            // use ori thinning image as guidance
            LineSegment2D[] lines = ori_thin_img.HoughLinesBinary(
                1,               //Distance resolution in pixel-related units
                Math.PI / 180.0, //Angle resolution measured in radians.
                3,               //threshold
                4,               //min Line width
                1                //gap between lines
                )[0];            //Get the lines from the first channel

            skel_lines.Clear();
            foreach (LineSegment2D line in lines)
            {
                skel_lines.Add(new Line2(new Vector2(line.P1.X, line.P1.Y), new Vector2(line.P2.X, line.P2.Y)));
            }

            Line2 bestline = FitSimilarLine(skel_lines, main_axis);
            if (Line2.IsParallel(bestline, main_axis, 5))
            {
                new_main_axis = bestline;
            }
            else
            {
                new_main_axis = main_axis;
            }
        }

        // update end
        List <Vector2> ori_skel_points = IExtension.GetMaskPoints(ori_thin_img);

        for (int i = 0; i < ori_skel_points.Count; i++)
        {
            new_main_axis.UpdateEnd(ori_skel_points[i]);
        }
        new_main_axis.start = new_main_axis.ProjToLine(face_center.First());
        new_main_axis.start = new_main_axis.GetPointwithT(4);

        List <Vector2> axis_points = new List <Vector2>();

        // axis_points.Add(face_center.First());
        axis_points.AddRange(new_main_axis.SamplePoints());
        axis_points.Add(new_main_axis.GetPointwithT((float)new_main_axis.Length() * 1.2f));
        axis_points = IExtension.ResetPath(axis_points, 3);

        #region visualize
        Image <Rgb, byte> mainaxis_point_img = body_img.Copy().Convert <Rgb, byte>();
        foreach (Vector2 v in axis_points)
        {
            mainaxis_point_img.Draw(new CircleF(new PointF(v.x, v.y), 2.0f), new Rgb(255, 0, 0), 1);
        }
        mainaxis_point_img.Draw(new CircleF(new PointF(axis_points.First().x, axis_points.First().y), 3.0f), new Rgb(0, 255, 0), 1);
        mainaxis_point_img.Save(index_forname.ToString() + this.label_forname.ToString() + "_axis_straight.png");
        #endregion

        return(axis_points);
    }
    private List <Line2> Skeletonize(out bool iscurve)
    {
        Image <Gray, byte> img2   = body_img.Copy();
        Image <Gray, byte> eroded = new Image <Gray, byte>(img2.Size);
        Image <Gray, byte> temp   = new Image <Gray, byte>(img2.Size);
        Image <Gray, byte> skel   = new Image <Gray, byte>(img2.Size);

        body_img.Save("test.png");


        #region with matlab
        string argument1 = "\"" + "test.png" + "\"";
        System.Diagnostics.Process process = new System.Diagnostics.Process();
        process.StartInfo.FileName               = System.Environment.CurrentDirectory + "\\Assets\\frommatlab\\skeleton.exe";
        process.StartInfo.Arguments              = argument1;
        process.StartInfo.UseShellExecute        = false;
        process.StartInfo.CreateNoWindow         = true;
        process.StartInfo.RedirectStandardOutput = true;
        //启动
        process.Start();
        process.WaitForExit();
        #endregion

        skel          = new Image <Gray, byte>("prune.png");
        ori_thin_img  = new Image <Gray, byte>("thin.png");
        ori_prune_img = skel;

        #region thining - comment
        //skel.SetValue(0);
        //CvInvoke.Threshold(img2, temp, 127, 256, 0);
        //var element = CvInvoke.GetStructuringElement(ElementShape.Cross, new Size(3, 3), new Point(-1, -1));
        //bool done = false;

        ////skeleton
        //int itr = 0;
        //while (!done)
        //{
        //    CvInvoke.Erode(img2, eroded, element, new Point(-1, -1), 1, BorderType.Reflect, default(MCvScalar));
        //    CvInvoke.Dilate(eroded, temp, element, new Point(-1, -1), 1, BorderType.Reflect, default(MCvScalar));
        //    CvInvoke.Subtract(img2, temp, temp);
        //    CvInvoke.BitwiseOr(skel, temp, skel);
        //    eroded.CopyTo(img2);
        //    itr++;
        //    if (CvInvoke.CountNonZero(img2) == 0) done = true;
        //}
        //Image<Gray, Byte> cannyimg = body_img.Canny(60, 100);
        //CvInvoke.Dilate(cannyimg, cannyimg, element, new Point(-1, -1), 3, BorderType.Reflect, default(MCvScalar));
        //CvInvoke.Subtract(skel, cannyimg, skel);
        //ori_skel_img = skel.Copy();

        ////thinning
        //if (!noface)
        //{
        //    #region thinning
        //    List<Mat> cs = new List<Mat>();
        //    List<Mat> ds = new List<Mat>();
        //    for (int i = 0; i < 8; i++)
        //    {
        //        cs.Add(CvInvoke.GetStructuringElement(ElementShape.Cross, new Size(3, 3), new Point(-1, -1)));
        //        ds.Add(CvInvoke.GetStructuringElement(ElementShape.Cross, new Size(3, 3), new Point(-1, -1)));
        //    }

        //    cs[0].SetTo(new int[] { 0, 0, 0, 0, 1, 0, 1, 1, 1 });
        //    cs[1].SetTo(new int[] { 1, 0, 0, 1, 1, 0, 1, 0, 0 });
        //    cs[2].SetTo(new int[] { 1, 1, 1, 0, 1, 0, 0, 0, 0 });
        //    cs[3].SetTo(new int[] { 0, 0, 1, 0, 1, 1, 0, 0, 1 });

        //    ds[0].SetTo(new int[] { 1, 1, 1, 0, 0, 0, 0, 0, 0 });
        //    ds[1].SetTo(new int[] { 0, 0, 1, 0, 0, 1, 0, 0, 1 });
        //    ds[2].SetTo(new int[] { 0, 0, 0, 0, 0, 0, 1, 1, 1 });
        //    ds[3].SetTo(new int[] { 1, 0, 0, 1, 0, 0, 1, 0, 0 });

        //    cs[4].SetTo(new int[] { 0, 0, 0, 1, 1, 0, 1, 1, 0 });
        //    cs[5].SetTo(new int[] { 1, 1, 0, 1, 1, 0, 0, 0, 0 });
        //    cs[6].SetTo(new int[] { 0, 1, 1, 0, 1, 1, 0, 0, 0 });
        //    cs[7].SetTo(new int[] { 0, 0, 0, 0, 1, 1, 0, 1, 1 });

        //    ds[4].SetTo(new int[] { 0, 1, 1, 0, 0, 1, 0, 0, 0 });
        //    ds[5].SetTo(new int[] { 0, 0, 0, 0, 0, 1, 0, 1, 1 });
        //    ds[6].SetTo(new int[] { 0, 0, 0, 1, 0, 0, 1, 1, 0 });
        //    ds[7].SetTo(new int[] { 1, 1, 0, 1, 0, 0, 0, 0, 0 });

        //    Image<Gray, byte> img3 = skel.Copy();
        //    Image<Gray, byte> temp2 = skel.CopyBlank();
        //    Image<Gray, byte> lastimg3 = skel.Copy();

        //    done = false;
        //    while (!done)
        //    {
        //        for (int i = 0; i < 8; i++)
        //        {
        //            temp = this.HitOrMiss(img3, cs[i], ds[i]);
        //            CvInvoke.Subtract(img3, temp, img3);
        //        }

        //        CvInvoke.Subtract(lastimg3, img3, temp2);
        //        lastimg3 = img3.Copy();
        //        if (CvInvoke.CountNonZero(temp2) == 0) done = true;
        //    }

        //    //img3.Save("thining.png");
        //    #endregion
        //    skel = img3.Copy();
        //    ori_thinning_img = img3.Copy();
        //}
        ////// remove noise
        ////for (int i = 0; i < img3.Height; i++)
        ////{
        ////    for (int j = 0; j < img3.Width; j++)
        ////    {
        ////        if (img3[i, j].Equals(new Gray(255)))
        ////        {
        ////            bool change = false;
        ////            for (int pad = 1; pad < 3; pad++)
        ////            {
        ////                if (i >= pad && i < img3.Height - pad && j >= pad && j < img3.Width - pad)
        ////                {
        ////                    if (img3[i - pad, j].Equals(new Gray(0)) &&
        ////                        img3[i - pad, j - pad].Equals(new Gray(0)) &&
        ////                        img3[i - pad, j + pad].Equals(new Gray(0)) &&
        ////                        img3[i + pad, j].Equals(new Gray(0)) &&
        ////                        img3[i + pad, j - pad].Equals(new Gray(0)) &&
        ////                        img3[i + pad, j + pad].Equals(new Gray(0)) &&
        ////                        img3[i, j - pad].Equals(new Gray(0)) &&
        ////                        img3[i, j + pad].Equals(new Gray(0)))
        ////                        change = true;
        ////                }
        ////            }
        ////            if (change)
        ////                img3[i, j] = new Gray(0);
        ////        }
        ////    }
        ////}
        ////img3.Save("thiningdenoise.png");
        #endregion

        // get line
        // consider both straight line and curve
        LineSegment2D[] lines = skel.HoughLinesBinary(
            1,               //Distance resolution in pixel-related units
            Math.PI / 180.0, //Angle resolution measured in radians.
            3,               //threshold
            4,               //min Line width
            1                //gap between lines
            )[0];            //Get the lines from the first channel

        Image <Gray, byte> lineimg    = skel.CopyBlank();
        List <Line2>       skel_lines = new List <Line2>();
        foreach (LineSegment2D line in lines)
        {
            //remove image boundaries
            //if (line.P1.X > 10 && line.P1.Y > 10 && line.P1.X < body_img.Height - 10 && line.P1.Y < body_img.Width &&
            //   line.P2.X > 10 && line.P2.Y > 10 && line.P2.X < body_img.Height - 10 && line.P2.Y < body_img.Width - 10)
            //{
            skel_lines.Add(new Line2(new Vector2(line.P1.X, line.P1.Y), new Vector2(line.P2.X, line.P2.Y)));
            lineimg.Draw(line, new Gray(255), 2);
            //}
        }
        if (debug)
        {
            lineimg.Save("skel-line.png");
        }


        // cluster according to direction and relative distance
        // too many cluster means curve axis
        IMGSIZE = Math.Min(body_img.Width, body_img.Height);
        if (skel_lines.Count > 0)
        {
            double[][] xy = new double[skel_lines.Count][];
            for (int i = 0; i < skel_lines.Count; i++)
            {
                xy[i] = new double[] { skel_lines[i].start.x, skel_lines[i].start.y,
                                       skel_lines[i].end.x, skel_lines[i].end.y };
            }

            MeanShift clusterMS = new MeanShift(4, new UniformKernel(), 0.02);
            clusterMS.Distance = new myDistanceClass();
            MeanShiftClusterCollection clustering = clusterMS.Learn(xy);
            var lineLabels   = clustering.Decide(xy);
            int clustercount = lineLabels.DistinctCount();
            //Debug.Log("cluster count: " + clustercount);

            if (debug)
            {
                Image <Rgb, byte> lineimg_rgb = lineimg.Convert <Rgb, byte>();
                System.Random     rnd         = new System.Random();
                Rgb[]             colortable  = new Rgb[clustering.Count];
                for (int i = 0; i < clustering.Count; i++)
                {
                    colortable[i] = new Rgb(rnd.Next(255), rnd.Next(255), rnd.Next(255));
                }

                for (int i = 0; i < skel_lines.Count; i++)
                {
                    int label = lineLabels[i];
                    lineimg_rgb.Draw(skel_lines[i].ToLineSegment2D(), colortable[label], 2);
                }
                lineimg_rgb.Save("skel-line-cluster.png");
            }


            if (noface)
            {
                thred = 2;   // 2
            }
            if (clustercount > thred)
            {
                iscurve = true;
            }
            else
            {
                iscurve = false;
            }
        }
        else
        {
            iscurve = false;
            NumericalRecipes.RansacLine2d rcl = new NumericalRecipes.RansacLine2d();
            List <Vector2> linepoints         = new List <Vector2>();
            linepoints = IExtension.GetMaskPoints(skel);
            Line2 bestline = rcl.Estimate(linepoints);
            skel_lines.Add(bestline);
        }

        return(skel_lines);
    }