private Line2 FitSimilarLine(List <Line2> lines, Line2 target, double angle = 20)
    {
        NumericalRecipes.RansacLine2d rcl = new NumericalRecipes.RansacLine2d();
        List <Vector2> linepoints         = new List <Vector2>();

        linepoints.AddRange(target.SamplePoints());
        //ransac
        for (int i = 0; i < lines.Count; i++)
        {
            if (Line2.IsParallel(lines[i], target, angle))
            {
                if (target.DistanceToLine(lines[i].start) < 30 && target.DistanceToLine(lines[i].end) < 30)
                {
                    linepoints.AddRange(lines[i].SamplePoints());
                }
            }
        }
        Line2 bestline = rcl.Estimate(linepoints);

        if (bestline == null)
        {
            return(target);
        }
        if (Vector2.Dot(bestline.dir, target.dir) < 0)
        {
            bestline.Flip();
        }
        return(bestline);
    }
    private Line2 FitCloseLine(List <Line2> lines, Line2 target, double dis = 30)
    {
        List <Vector2> linepoints = new List <Vector2>();

        linepoints.AddRange(target.SamplePoints());
        //ransac
        for (int i = 0; i < lines.Count; i++)
        {
            if (Line2.IsParallel(lines[i], target, 45))
            {
                double d = double.MaxValue;
                foreach (Vector2 v in linepoints)
                {
                    d = Math.Min(Math.Min(Vector2.Distance(lines[i].start, v), Vector2.Distance(lines[i].end, v)), d);
                }
                if (d < dis)
                {
                    linepoints.AddRange(lines[i].SamplePoints());
                }
            }
        }

        // least square
        //Vector2 dir = Utility.GetDirection(linepoints);
        //Line2 bestline = new Line2(linepoints[0], dir, true);
        //foreach (Vector2 v in linepoints)
        //    bestline.UpdateEnd(v);

        //ransac
        NumericalRecipes.RansacLine2d rcl = new NumericalRecipes.RansacLine2d();
        Line2 bestline = rcl.Estimate(linepoints);

        if (bestline == null)
        {
            return(target);
        }

        if (Vector2.Dot(bestline.dir, target.dir) < 0)
        {
            bestline.Flip();
        }
        return(bestline);
    }
    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 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 Line2 ApproximateFromCubeTop()
    {
        #region get corner points
        // get corner points
        VectorOfVectorOfPoint con      = new VectorOfVectorOfPoint();
        Image <Gray, byte>    img_copy = this.face_img[0].Copy();
        CvInvoke.FindContours(img_copy, con, img_copy, RetrType.Ccomp, ChainApproxMethod.ChainApproxSimple);
        int maxcomp = 0, max_con_idx = -1;
        for (int i = 0; i < con.Size; i++)
        {
            if (con[i].Size > maxcomp)
            {
                maxcomp     = con[i].Size;
                max_con_idx = i;
            }
        }
        // found max component's contour
        // simplify contours
        VectorOfPoint con2 = new VectorOfPoint();
        con2 = con[max_con_idx];
        double         alpha         = 0.01; // 越小越接近曲线
        int            iter          = 0;
        List <Vector2> corner_points = new List <Vector2>();
        while (con2.Size != 4)
        {
            if (iter++ > 200)
            {
                break;
            }
            double epsilon = alpha * CvInvoke.ArcLength(con[max_con_idx], true);
            CvInvoke.ApproxPolyDP(con[max_con_idx], con2, epsilon, true);
            if (con2.Size > 4)
            {
                alpha += 0.01;
                corner_points.Clear();
                for (int i = 0; i < con2.Size; i++)
                {
                    corner_points.Add(new Vector2(con2[i].X, con2[i].Y));
                }
            }
            if (con2.Size < 4)
            {
                alpha -= 0.003;
            }
        }

        if (con2.Size == 4)
        {
            corner_points.Clear();
            for (int i = 0; i < con2.Size; i++)
            {
                corner_points.Add(new Vector2(con2[i].X, con2[i].Y));
            }
        }
        else
        {
            int removei = corner_points.Count - 4;
            for (int i = 0; i < removei; i++)
            {
                corner_points.RemoveAt(1);
            }
        }


        face_center = new List <Vector2>();
        face_center.Add(Utility.Average(corner_points));


        List <Vector2> boundary2_face = IExtension.GetBoundary(this.face_img[0]);
        List <Vector2> boundary2_body = ExtractOutline(this.body_img, boundary2_face);
        int[]          i_corner       = SelectTwoCorners(corner_points, boundary2_body, this.body_img);
        #endregion


        // body img fit line
        Image <Gray, byte> temp       = body_img.Copy();
        Image <Gray, byte> body_bound = temp.Canny(60, 100);

        LineSegment2D[] lines = body_bound.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

        var element = CvInvoke.GetStructuringElement(ElementShape.Cross, new Size(3, 3), new Point(-1, -1));
        CvInvoke.Dilate(this.face_img[0], temp, element, new Point(-1, -1), 30, BorderType.Reflect, default(MCvScalar));
        //temp.Save("face_dilate.png");
        Image <Gray, byte> lineimg    = temp.CopyBlank();
        List <Line2>       body_lines = new List <Line2>();
        foreach (LineSegment2D line in lines)
        {
            if (!temp[line.P1.Y, line.P1.X].Equals(new Gray(255)) && !temp[line.P2.Y, line.P2.X].Equals(new Gray(255)))
            {
                body_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);
            }
        }
        //lineimg.Save("body_line.png");


        /// use corner points to find lines
        double mindis0 = double.MaxValue, mindis1 = double.MaxValue;
        int    line0 = -1, line1 = -1;
        for (int i = 0; i < body_lines.Count; i++)
        {
            double dis0 = Mathf.Min(Vector2.Distance(corner_points[i_corner[0]], body_lines[i].start), Vector2.Distance(corner_points[i_corner[0]], body_lines[i].end));
            double dis1 = Mathf.Min(Vector2.Distance(corner_points[i_corner[1]], body_lines[i].start), Vector2.Distance(corner_points[i_corner[1]], body_lines[i].end));
            if (dis0 < mindis0)
            {
                mindis0 = dis0;
                line0   = i;
            }
            if (dis1 < mindis1)
            {
                mindis1 = dis1;
                line1   = i;
            }
        }

        // find similar
        // straight line

        Line2 body_line0 = FitCloseLine(body_lines, body_lines[line0], 50);
        Line2 body_line1 = FitCloseLine(body_lines, body_lines[line1], 50);

        if (Vector2.Distance(corner_points[i_corner[0]], body_line0.start) < Vector2.Distance(corner_points[i_corner[0]], body_line0.end))
        {
            body_line0.Flip();
        }

        if (Vector2.Distance(corner_points[i_corner[1]], body_line1.start) < Vector2.Distance(corner_points[i_corner[1]], body_line1.end))
        {
            body_line1.Flip();
        }

        Vector2 meandir = (body_line0.dir + body_line1.dir).normalized;

        Line2   top_normal_line = new Line2(face_center.First(), meandir, true);
        Vector2 online_point    = top_normal_line.GetPointwithT(Vector2.Distance(corner_points[0], corner_points[1]));
        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();
        }
        top_normal_line.UpdateEnd(new Vector2(500, 500));
        top_normal_line.UpdateEnd(new Vector2(0, 0));

        if (debug)
        {
            Image <Rgb, byte> cube_outline = this.body_img.Copy().Convert <Rgb, byte>();
            cube_outline.Draw(new CircleF(new PointF(corner_points[i_corner[0]].x, corner_points[i_corner[0]].y), 10.0f), new Rgb(255, 0, 0), 1);
            cube_outline.Draw(new CircleF(new PointF(corner_points[i_corner[1]].x, corner_points[i_corner[1]].y), 10.0f), new Rgb(255, 0, 0), 1);
            cube_outline.Draw(new CircleF(new PointF(corner_points[0].x, corner_points[0].y), 8), new Rgb(0, 0, 255), 1);
            cube_outline.Draw(new CircleF(new PointF(corner_points[1].x, corner_points[1].y), 8), new Rgb(0, 0, 255), 1);
            cube_outline.Draw(new CircleF(new PointF(corner_points[2].x, corner_points[2].y), 8), new Rgb(0, 0, 255), 1);
            cube_outline.Draw(new CircleF(new PointF(corner_points[3].x, corner_points[3].y), 8), new Rgb(0, 0, 255), 1);
            cube_outline.Draw(body_lines[line0].ToLineSegment2D(), new Rgb(0, 255, 0), 3);
            cube_outline.Draw(body_lines[line1].ToLineSegment2D(), new Rgb(0, 255, 0), 3);
            cube_outline.Draw(body_line0.ToLineSegment2D(), new Rgb(0, 255, 255), 3);
            cube_outline.Draw(body_line1.ToLineSegment2D(), new Rgb(0, 0, 255), 3);
            cube_outline.Save("cube_outline.png");
            Image <Gray, byte> approx_top = this.body_img.Copy();
            foreach (Vector2 v in top_normal_line.SamplePoints())
            {
                approx_top.Draw(new CircleF(new PointF(v.x, v.y), 2.0f), new Gray(0), 1);
            }
            approx_top.Save("approx.png");
        }

        return(top_normal_line);
    }