using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows.Forms;

namespace Tongue_extraction
{
    public partial class ColorExtractor
    {
        // Config
        const int RADIUS_COLORAREA = 10;
        public static Bitmap bitmap;
        float[] a = new float[17];
        float[] b = new float[17];
        float[] c = new float[17];
        float d;
        float e;
        float f;
        int k;
        public static bool m_getColor = false;
        public static Mat m_CalibFrame; //キャリブレーション用画像
        public static OpenCvSharp.Point[] getRGBpoint = new OpenCvSharp.Point[24];//RGB取得用
        double[] m_BforLab = new double[24];
        double[] m_GforLab = new double[24];
        double[] m_RforLab = new double[24];
        public static bool m_bCalib;
        public static OpenCvSharp.Point pt = new OpenCvSharp.Point();//キャリブレーション用のポイント入れ

        public enum FivePointMethod {Method1, Method2, Method3};

        public List<OpenCvSharp.Point> Get5points(Mat mat_finalMask, FivePointMethod method)
        {
            // 表示用
            var mat_dst = mat_finalMask.Clone();
            Cv2.CvtColor(mat_dst, mat_dst, ColorConversionCodes.GRAY2BGR);

            // マスクの舌領域画素座標
            var mat_nonZeroCoordinates = new Mat();
            Cv2.FindNonZero(mat_finalMask, mat_nonZeroCoordinates);

            // 舌領域上の点をすべてlistに詰める
            var list_X = new List<int>();
            var list_Y = new List<int>();
            for (int i = 0; i < mat_nonZeroCoordinates.Total(); i++)
            {
                var x = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(i).X;
                var y = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(i).Y;
                list_X.Add(x);
                list_Y.Add(y);
            }

            if(method == FivePointMethod.Method1)
            {
                /// method1
                // 端っこを探索(ラスタ左上から)
                var p_top = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(list_Y.IndexOf(list_Y.Min()));
                var p_bottom = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(list_Y.IndexOf(list_Y.Max()));
                var p_left = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(list_X.IndexOf(list_X.Min()));
                var p_right = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(list_X.IndexOf(list_X.Max()));

                // 舌尖領域を示すy座標を取得(割合を今回は決め打ち)
                var y_apex = (int)(p_top.Y + ((p_bottom.Y - p_top.Y) * 0.8));
                var p_apex_left = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(IndexOfAll(list_Y, y_apex).Min());
                var p_apex_right = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(IndexOfAll(list_Y, y_apex).Max());

                // 表示してみる
                //Cv2.Circle(mat_dst, p_top, 20, new Scalar(255, 255, 0), -1);
                //Cv2.Circle(mat_dst, p_bottom, 20, new Scalar(255, 255, 0), -1);
                //Cv2.Circle(mat_dst, p_left, 20, new Scalar(255, 255, 0), -1);
                //Cv2.Circle(mat_dst, p_right, 20, new Scalar(255, 255, 0), -1);
                //Cv2.Circle(mat_dst, p_apex_left, 20, new Scalar(255, 255, 0), -1);
                //Cv2.Circle(mat_dst, p_apex_right, 20, new Scalar(255, 255, 0), -1);

                mat_dst.Dispose();
                mat_nonZeroCoordinates.Dispose();
                GC.Collect();

                var li_dst = new List<OpenCvSharp.Point> { p_left, p_apex_left, p_bottom, p_apex_right, p_right };
                return li_dst;
            }
            else if (method == FivePointMethod.Method2)
            {
                /// method2
                // 重心(CoG)計算
                var moments = Cv2.Moments(mat_finalMask, true);
                var moment_x = moments.M10 / moments.M00;
                var moment_y = moments.M01 / moments.M00;

                // 輪郭座標
                OpenCvSharp.Point[][] contours;
                HierarchyIndex[] hierarchy;
                Cv2.FindContours(mat_finalMask, out contours, out hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple);
                var sortedContour = contours.OrderByDescending(n => Cv2.ContourArea(n)).ToList();
                var maxContour = sortedContour[0];

                // 重心-輪郭の距離
                double maxDistance_lefttop = 0.0;
                double maxDistance_righttop = 0.0;
                var p_left_2 = new OpenCvSharp.Point();
                var p_right_2 = new OpenCvSharp.Point();
                for (int i = 0; i < maxContour.Length; i++)
                {
                    // 重心より上側
                    if (maxContour[i].Y < moment_y)
                    {
                        // 重心より上側左側
                        if (maxContour[i].X < moment_x)
                        {
                            var distance = maxContour[i].DistanceTo(new OpenCvSharp.Point(moment_x, moment_y));
                            if (distance > maxDistance_lefttop)
                            {
                                maxDistance_lefttop = distance;
                                p_left_2 = maxContour[i];
                            }

                        }
                        // 重心より上側右側
                        if (maxContour[i].X >= moment_x)
                        {
                            var distance = maxContour[i].DistanceTo(new OpenCvSharp.Point(moment_x, moment_y));
                            if (distance > maxDistance_righttop)
                            {
                                maxDistance_righttop = distance;
                                p_right_2 = maxContour[i];
                            }
                        }
                    }
                }
                // 舌尖領域を示すy座標を取得(割合を今回は決め打ち)
                var p_bottom = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(list_Y.IndexOf(list_Y.Max()));
                var y_top_avg_ = (p_left_2.Y + p_right_2.Y) / 2.0;
                var y_apex_2 = (int)(y_top_avg_ + ((p_bottom.Y - y_top_avg_) * 0.57));
                var p_apex_left_2 = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(IndexOfAll(list_Y, y_apex_2).Min());
                var p_apex_right_2 = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(IndexOfAll(list_Y, y_apex_2).Max());

                // 表示
                //Cv2.Circle(mat_dst, p_left_2, 20, new Scalar(255, 0, 0), -1);
                //Cv2.Circle(mat_dst, p_right_2, 20, new Scalar(255, 0, 0), -1);
                //Cv2.Circle(mat_dst, p_bottom_2, 20, new Scalar(255, 0, 0), -1);
                //Cv2.Circle(mat_dst, p_apex_left_2, 20, new Scalar(255, 0, 0), -1);
                //Cv2.Circle(mat_dst, p_apex_right_2, 20, new Scalar(255, 0, 0), -1);
                //Cv2.Circle(mat_dst, new OpenCvSharp.Point(moment_x, moment_y), 10, new Scalar(200, 60, 200), -1); //重心
                //Cv2.DrawContours(mat_dst, contours, 0, new Scalar(0, 255, 255), 4); // 輪郭
                mat_dst.Dispose();
                mat_nonZeroCoordinates.Dispose();
                GC.Collect();

                var li_dst = new List<OpenCvSharp.Point> { p_left_2, p_apex_left_2, p_bottom, p_apex_right_2, p_right_2 };
                return li_dst;
            }
            else if (method == FivePointMethod.Method3)
            {
                /// method3
                // 重心(CoG)計算
                var moments = Cv2.Moments(mat_finalMask, true);
                var moment_x = moments.M10 / moments.M00;
                var moment_y = moments.M01 / moments.M00;

                // 輪郭座標
                OpenCvSharp.Point[][] contours;
                HierarchyIndex[] hierarchy;
                Cv2.FindContours(mat_finalMask, out contours, out hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple);
                var sortedContour = contours.OrderByDescending(n => Cv2.ContourArea(n)).ToList();
                var maxContour = sortedContour[0];

                // 重心-輪郭の距離
                double maxDistance_lefttop = 0.0;
                double maxDistance_righttop = 0.0;
                var p_left_3 = new OpenCvSharp.Point();
                var p_right_3 = new OpenCvSharp.Point();
                // 距離が最も遠いものを採択
                for (int i = 0; i < maxContour.Length; i++)
                {
                    // 重心より上側
                    if (maxContour[i].Y < moment_y)
                    {
                        // 重心より上側左側
                        if (maxContour[i].X < moment_x)
                        {
                            var distance = maxContour[i].DistanceTo(new OpenCvSharp.Point(moment_x, moment_y));
                            if (distance > maxDistance_lefttop)
                            {
                                maxDistance_lefttop = distance;
                                p_left_3 = maxContour[i];
                            }

                        }
                        // 重心より上側右側
                        if (maxContour[i].X >= moment_x)
                        {
                            var distance = maxContour[i].DistanceTo(new OpenCvSharp.Point(moment_x, moment_y));
                            if (distance > maxDistance_righttop)
                            {
                                maxDistance_righttop = distance;
                                p_right_3 = maxContour[i];
                            }
                        }
                    }
                }

                // 重心とtopの中点を算出する
                var p_top = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(list_Y.IndexOf(list_Y.Min()));
                var y_topToCoG = (int)(moment_y + p_top.Y) / 2;
                var p_topToCoG_left = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(IndexOfAll(list_Y, y_topToCoG).Min());
                var p_topToCoG_right = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(IndexOfAll(list_Y, y_topToCoG).Max());
                var p_topToCoG_center = new OpenCvSharp.Point((int)((p_topToCoG_left.X + p_topToCoG_right.X) / 2), y_topToCoG);
                // 重心とbottomの中点を算出する
                var p_bottom = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(list_Y.IndexOf(list_Y.Max()));
                var y_bottomToCoG = (int)(moment_y + p_bottom.Y) / 2;
                var p_bottomToCoG_left = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(IndexOfAll(list_Y, y_bottomToCoG).Min());
                var p_bottomToCoG_right = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(IndexOfAll(list_Y, y_bottomToCoG).Max());
                var p_bottomToCoG_center = new OpenCvSharp.Point((int)((p_bottomToCoG_left.X + p_bottomToCoG_right.X) / 2), y_bottomToCoG);

                // 直線フィッティング
                var lefty = new OpenCvSharp.Point();
                var righty = new OpenCvSharp.Point();
                if (p_topToCoG_center.X == p_bottomToCoG_center.X)
                {
                    // 傾き0の場合の例外処理
                    lefty.X = p_topToCoG_center.X;
                    lefty.Y = 0;
                    righty.X = p_topToCoG_center.X;
                    righty.Y = mat_finalMask.Rows - 1;
                }
                else
                {
                    var line = Cv2.FitLine(new OpenCvSharp.Point[2] { p_topToCoG_center, p_bottomToCoG_center }, DistanceTypes.L2, 0, 0.01, 0.01);
                    lefty.X = 0;
                    lefty.Y = (int)((-line.X1 * line.Vy / line.Vx) + line.Y1);
                    righty.X = mat_finalMask.Cols - 1;
                    righty.Y = (int)(((mat_finalMask.Cols - line.X1) * line.Vy / line.Vx) + line.Y1);

                }
                // 直線上の輪郭点
                var mat_centerline = new Mat(mat_finalMask.Size(), MatType.CV_8UC1, 0);
                var mat_contour = new Mat(mat_finalMask.Size(), MatType.CV_8UC1, 0);
                var mat_and = new Mat(mat_finalMask.Size(), MatType.CV_8UC1, 0);
                Cv2.Line(mat_centerline, lefty, righty, 3);
                Cv2.DrawContours(mat_contour, sortedContour, 0, 3);
                Cv2.BitwiseAnd(mat_centerline, mat_contour, mat_and);
                Cv2.FindNonZero(mat_and, mat_and);
                var bottom_y_3 = 0;
                var bottom_x_3 = 0;
                for (int i = 0; i < mat_and.Total(); i++)
                {
                    var x = mat_and.At<OpenCvSharp.Point>(i).X;
                    var y = mat_and.At<OpenCvSharp.Point>(i).Y;
                    if (bottom_y_3 < y)
                    {
                        bottom_y_3 = y;
                        bottom_x_3 = x;
                    }
                }
                var p_bottom_3 = new OpenCvSharp.Point(bottom_x_3, bottom_y_3);

                // 舌尖領域を示すy座標を取得(割合を今回は決め打ち)
                var y_top_avg_ = (p_left_3.Y + p_right_3.Y) / 2.0;
                var y_apex_3 = (int)(y_top_avg_ + ((p_bottom_3.Y - y_top_avg_) * 0.57));
                var p_apex_left_3 = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(IndexOfAll(list_Y, y_apex_3).Min());
                var p_apex_right_3 = mat_nonZeroCoordinates.At<OpenCvSharp.Point>(IndexOfAll(list_Y, y_apex_3).Max());

                //Cv2.Circle(mat_dst, p_topToCoG_left, 20, new Scalar(0, 100, 255), -1);
                //Cv2.Circle(mat_dst, p_topToCoG_right, 20, new Scalar(0, 100, 255), -1);
                //Cv2.Circle(mat_dst, p_topToCoG_center, 20, new Scalar(0, 0, 255), -1);
                //Cv2.Circle(mat_dst, p_bottomToCoG_left, 20, new Scalar(0, 100, 255), -1);
                //Cv2.Circle(mat_dst, p_bottomToCoG_right, 20, new Scalar(0, 100, 255), -1);
                //Cv2.Circle(mat_dst, p_bottomToCoG_center, 20, new Scalar(0, 0, 255), -1);
                //Cv2.Circle(mat_dst, p_bottom_3, 20, new Scalar(0, 0, 255), -1);

                // 破棄
                mat_dst.Dispose();
                mat_nonZeroCoordinates.Dispose();
                GC.Collect();

                var li_dst = new List<OpenCvSharp.Point> { p_left_3, p_apex_left_3, p_bottom_3, p_apex_right_3, p_right_3 };
                return li_dst;
            }
            else
            {
                return new List<OpenCvSharp.Point>();
            }
            // DEBUG
            //Cv2.NamedWindow("dst", WindowMode.KeepRatio ^ WindowMode.AutoSize);
            //Cv2.ImShow("dst", mat_dst.Resize(new OpenCvSharp.Size((int)mat_dst.Width * 0.5, (int)mat_dst.Height * 0.5)));
            
        }

        public List<OpenCvSharp.Point> Get8area(List<OpenCvSharp.Point> list_5points)
        {
            var li_dst = new List<OpenCvSharp.Point>();
            // ROIマスク画像1
            // 0____________ 4
            //  |           |
            //  |           |
            //  |           |
            // 1|●       ●|3
            //  舌　　　   /
            //   舌_______/
            //        2

            var points1 = new int[2, 3] { { 0, 2, 1 }, { 4, 2, 3 } };
            var area1 = new OpenCvSharp.Point();
            for (int i = 0; i < 2; i++)
            {
                var cx1 = (list_5points[points1[i, 0]].X + list_5points[points1[i, 1]].X) / 2;
                var cy1 = (list_5points[points1[i, 0]].Y + list_5points[points1[i, 1]].Y) / 2;
                area1.X = (list_5points[points1[i, 2]].X + cx1) / 2;
                area1.Y = (list_5points[points1[i, 2]].Y + cy1) / 2;
                li_dst.Add(area1);
            }

            // ROIマスク画像2
            // 0____________ 4
            //  |   ● ●   |
            //  |           |
            //  |           |
            // 1|           |3
            //  舌　　　   /
            //   舌_______/
            //        2

            var points2 = new int[2, 4] { { 0, 3, 0, 4 }, { 4, 1, 4, 0 } };
            var area2 = new OpenCvSharp.Point();
            for (int i = 0; i < 2; i++)
            {
                float cx1 = list_5points[points2[i, 0]].X + (list_5points[points2[i, 1]].X - list_5points[points2[i, 0]].X) / 4;
                float cy1 = list_5points[points2[i, 0]].Y + (list_5points[points2[i, 1]].Y - list_5points[points2[i, 0]].Y) / 4;
                float cx2 = (list_5points[points2[i, 2]].X + list_5points[points2[i, 3]].X) / 2;
                float cy2 = (list_5points[points2[i, 2]].Y + list_5points[points2[i, 3]].Y) / 2;
                area2.X = (int)(cx1 + cx2) / 2;
                area2.Y = (int)(cy1 + cy2) / 2;
                li_dst.Add(area2);
            }

            // ROIマスク画像3
            // 0____________ 4
            //  |           |
            //  |           |
            //  |           |
            // 1|   ● ●   |3
            //  舌　　　   /
            //   舌_______/
            //        2
            var points3 = new int[2, 3] { { 0, 3, 2 }, { 4, 1, 2 } };
            var area3 = new OpenCvSharp.Point();
            for (int i = 0; i < 2; i++)
            {
                float cx1 = list_5points[points3[i, 0]].X + (list_5points[points3[i, 1]].X - list_5points[points3[i, 0]].X) / 4;
                float cy1 = list_5points[points3[i, 0]].Y + (list_5points[points3[i, 1]].Y - list_5points[points3[i, 0]].Y) / 4;
                area3.X = (int)(list_5points[points3[i, 2]].X + cx1) / 2;
                area3.Y = (int)(list_5points[points3[i, 2]].Y + cy1) / 2;
                li_dst.Add(area3);
            }

            // ROIマスク画像3
            // 0____________ 4
            //  |           |
            //  |           |
            //  |           |
            // 1|           |3
            //  舌　　　   /
            //   舌_●_●_/
            //        2
            var points4 = new int[2, 2] { { 0, 2 }, { 4, 2 } };
            var area4 = new OpenCvSharp.Point();
            for (int i = 0; i < 2; i++)
            {
                area4.X = list_5points[points4[i, 0]].X + (list_5points[points4[i, 1]].X - list_5points[points4[i, 0]].X) * 7 / 8;
                area4.Y = list_5points[points4[i, 0]].Y + (list_5points[points4[i, 1]].Y - list_5points[points4[i, 0]].Y) * 7 / 8;
                li_dst.Add(area4);
            }

            // To do : もし8areaが舌領域に載っていなかったら修正
            //bool isOnTongueArea = DiscriminateOnTongueArea(li_dst);
            //if (isOnTongueArea)
            //{

            //}

            return li_dst;
        }

        public List<OpenCvSharp.Scalar> Get8colors(Mat mat_maskedImg, List<OpenCvSharp.Point> list_8area)
        {
            List<OpenCvSharp.Scalar> li_dst = new List<Scalar>();
            for (int i = 0; i < list_8area.Count(); i++)
            {
                using (Mat mat_colorRoi = Mat.Zeros(mat_maskedImg.Size(), MatType.CV_8UC1))
                {
                    // 色抽出領域を示すマスク画像を作成
                    Cv2.Circle(mat_colorRoi, list_8area[i], RADIUS_COLORAREA, 255, -1);

                    // 領域で色を抽出
                    var color = Cv2.Mean(mat_maskedImg, mat_colorRoi);
                    li_dst.Add(color);
                }
            }
            return li_dst;
        }

        public void Show8area(Mat oriImg, List<OpenCvSharp.Point> list_8area)
        {

            Cv2.Circle(oriImg, list_8area[0], 10, new Scalar(0, 255, 0), -1);
            Cv2.Circle(oriImg, list_8area[1], 10, new Scalar(0, 255, 0), -1);
            Cv2.Circle(oriImg, list_8area[2], 10, new Scalar(255, 255, 255), -1);
            Cv2.Circle(oriImg, list_8area[3], 10, new Scalar(255, 255, 255), -1);
            Cv2.Circle(oriImg, list_8area[4], 10, new Scalar(255, 255, 0), -1);
            Cv2.Circle(oriImg, list_8area[5], 10, new Scalar(255, 255, 0), -1);
            Cv2.Circle(oriImg, list_8area[6], 10, new Scalar(255, 0, 0), -1);
            Cv2.Circle(oriImg, list_8area[7], 10, new Scalar(255, 0, 0), -1);
            Cv2.NamedWindow("dst_", WindowMode.KeepRatio ^ WindowMode.Normal);
            Cv2.ImShow("dst_", oriImg.Resize(new OpenCvSharp.Size((int)oriImg.Width * 0.5, (int)oriImg.Height * 0.5)));
        }

        public Mat ShowResult(Mat oriImg, List<OpenCvSharp.Point> list_5point, List<OpenCvSharp.Point> list_8area)
        {
            Cv2.Circle(oriImg, list_5point[0], 3, new Scalar(255, 0, 0), -1);
            Cv2.Circle(oriImg, list_5point[1], 3, new Scalar(255, 0, 0), -1);
            Cv2.Circle(oriImg, list_5point[2], 3, new Scalar(255, 0, 0), -1);
            Cv2.Circle(oriImg, list_5point[3], 3, new Scalar(255, 0, 0), -1);
            Cv2.Circle(oriImg, list_5point[4], 3, new Scalar(255, 0, 0), -1);

            Cv2.Circle(oriImg, list_8area[0], 10, new Scalar(0, 255, 0), -1);
            Cv2.Circle(oriImg, list_8area[1], 10, new Scalar(0, 255, 0), -1);
            Cv2.Circle(oriImg, list_8area[2], 10, new Scalar(0, 255, 255), -1);
            Cv2.Circle(oriImg, list_8area[3], 10, new Scalar(0, 255, 255), -1);
            Cv2.Circle(oriImg, list_8area[4], 10, new Scalar(255, 255, 0), -1);
            Cv2.Circle(oriImg, list_8area[5], 10, new Scalar(255, 255, 0), -1);
            Cv2.Circle(oriImg, list_8area[6], 10, new Scalar(255, 0, 255), -1);
            Cv2.Circle(oriImg, list_8area[7], 10, new Scalar(255, 0, 255), -1);

            Cv2.Line(oriImg, list_5point[0], list_5point[1], new Scalar(255, 100, 100));
            Cv2.Line(oriImg, list_5point[1], list_5point[2], new Scalar(255, 100, 100));
            Cv2.Line(oriImg, list_5point[2], list_5point[3], new Scalar(255, 100, 100));
            Cv2.Line(oriImg, list_5point[3], list_5point[4], new Scalar(255, 100, 100));
            Cv2.Line(oriImg, list_5point[4], list_5point[0], new Scalar(255, 100, 100));

            return oriImg;
        }

        public List<OpenCvSharp.Scalar> Calc8Lab(List<OpenCvSharp.Scalar> list_8colors, string path_calibCsv, string path_colorMatrixXYZ)
        {
            GetColorMatrixRGB(path_calibCsv);
            CalcTransMat(path_colorMatrixXYZ);
            Read_TranslationMatrix();
            var li_dst = new List<OpenCvSharp.Scalar>();
            for (int i = 0; i < list_8colors.Count(); i++)
            {
                var LabValue = CalcLab(list_8colors[i]);
                li_dst.Add(LabValue);
            }
            return li_dst;
        }

        public void GetColorMatrixRGB(string path_calibCsv)
        {
            System.Text.Encoding encoding = GetType(path_calibCsv);
            System.IO.FileStream fs3 = new System.IO.FileStream(path_calibCsv, System.IO.FileMode.Open, System.IO.FileAccess.Read);
            System.IO.StreamReader sr3 = new System.IO.StreamReader(fs3, encoding);
            string strLine = "";
            string[] aryLine;
            sr3.ReadLine(); // headerをスルー
            int i = 0;
            while ((strLine = sr3.ReadLine()) != null)
            {
                aryLine = strLine.Split(',');
                var no = Convert.ToSingle(aryLine[0]);
                float r = Convert.ToSingle(aryLine[1]);
                float g = Convert.ToSingle(aryLine[2]);
                float b = Convert.ToSingle(aryLine[3]);
                m_BforLab[i] = b;
                m_GforLab[i] = g;
                m_RforLab[i] = r;
                i++;
            }
            sr3.Close();
            fs3.Close();
        }

        public void CalcTransMat(string path_colorMatrixXYZ)
        {
            Mat RGBmat = new Mat(24, 17, MatType.CV_64F, new Scalar(1.0f));
            Mat XYZmat = new Mat(24, 4, MatType.CV_64F, new Scalar(1.0f));

            // ColorChartのXYZ読み込み
            string line;
            string[] split = new string[3];
            double valueX = 0, valueY = 0, valueZ = 0;
            System.Text.Encoding encoding3 = GetType(path_colorMatrixXYZ);
            System.IO.FileStream fs3 = new System.IO.FileStream(path_colorMatrixXYZ, System.IO.FileMode.Open, System.IO.FileAccess.Read);
            System.IO.StreamReader sr3 = new System.IO.StreamReader(fs3, encoding3);
            for (int i = 0; i < 24; i++)
            {
                line = sr3.ReadLine();
                split = line.Split(' ');
                valueX = Convert.ToDouble(split[0]);
                valueY = Convert.ToDouble(split[1]);
                valueZ = Convert.ToDouble(split[2]);
                XYZmat.Set<double>(i, 0, valueX);
                XYZmat.Set<double>(i, 1, valueY);
                XYZmat.Set<double>(i, 2, valueZ);
            }
            sr3.Close();
            fs3.Close();

            // 変換行列の計算
            for (int j = 0; j < 24; j++)
            {
                //順番
                //R,G,B,RG,RB,GB,R^2,G^2,B^2
                //R^2B,R^2G,G^2,R,G^2B,B^2R,B^2G,RGB
                RGBmat.Set<double>(j, 2, m_BforLab[j]);
                RGBmat.Set<double>(j, 1, m_GforLab[j]);
                RGBmat.Set<double>(j, 0, m_RforLab[j]);
                //2V次の項						  
                RGBmat.Set<double>(j, 3, m_RforLab[j] * m_GforLab[j]);
                RGBmat.Set<double>(j, 4, m_RforLab[j] * m_BforLab[j]);
                RGBmat.Set<double>(j, 5, m_GforLab[j] * m_BforLab[j]);
                RGBmat.Set<double>(j, 6, m_RforLab[j] * m_RforLab[j]);
                RGBmat.Set<double>(j, 7, m_GforLab[j] * m_GforLab[j]);
                RGBmat.Set<double>(j, 8, m_BforLab[j] * m_BforLab[j]);
                //3V次の項
                RGBmat.Set<double>(j, 9, m_RforLab[j] * m_RforLab[j] * m_BforLab[j]);
                RGBmat.Set<double>(j, 10, m_RforLab[j] * m_RforLab[j] * m_GforLab[j]);
                RGBmat.Set<double>(j, 11, m_GforLab[j] * m_GforLab[j] * m_RforLab[j]);
                RGBmat.Set<double>(j, 12, m_GforLab[j] * m_GforLab[j] * m_BforLab[j]);
                RGBmat.Set<double>(j, 13, m_BforLab[j] * m_BforLab[j] * m_RforLab[j]);
                RGBmat.Set<double>(j, 14, m_BforLab[j] * m_BforLab[j] * m_GforLab[j]);
                RGBmat.Set<double>(j, 15, m_RforLab[j] * m_BforLab[j] * m_GforLab[j]);
            }
            // 変換行列の生成
            Mat translation = new Mat();
            var canSolve = Cv2.Solve(RGBmat, XYZmat, translation, DecompTypes.SVD);

            // 保存
            string CSVfilename = "translateMatrix.csv";
            FileStream CSV_file = File.Open(CSVfilename, FileMode.OpenOrCreate, FileAccess.Write);
            CSV_file.Seek(0, SeekOrigin.Begin);
            CSV_file.SetLength(0);
            CSV_file.Close();
            StreamWriter CSV_data = new StreamWriter(CSVfilename);
            string s2 = "";
            for (int i = 0; i <= 16; i++)
            {
                for (int j = 0; j <= 2; j++)
                {
                    double s1 = translation.At<double>(i, j);
                    s2 += s1.ToString() + ",";
                }
                CSV_data.WriteLine(s2);
                s2 = "";
            }
            CSV_data.Close();
        }

        public void Read_TranslationMatrix()
        {
            // 変換行列を読み込みなおす
            System.Text.Encoding encoding = GetType("translateMatrix.csv");
            System.IO.FileStream fs1 = new System.IO.FileStream("translateMatrix.csv", System.IO.FileMode.Open, System.IO.FileAccess.Read);
            System.IO.StreamReader sr1 = new System.IO.StreamReader(fs1, encoding);
            for (int i = 0; i < 17; i++)
            {
                a[i] = 0;
                b[i] = 0;
                c[i] = 0;
            }
            k = 0;
            string strLine = "";
            string[] aryLine = null;
            while ((strLine = sr1.ReadLine()) != null)
            {
                aryLine = strLine.Split(',');
                a[k] = Convert.ToSingle(aryLine[0]);
                b[k] = Convert.ToSingle(aryLine[1]);
                c[k] = Convert.ToSingle(aryLine[2]);
                k++;
            }
            sr1.Close();
            fs1.Close();
        }

        private OpenCvSharp.Scalar CalcLab(OpenCvSharp.Scalar BgrValue)
        {
            // CIELabの計算
            // XYZに変換
            double X, Y, Z;
            double m_B = BgrValue.Val0;
            double m_G = BgrValue.Val1;
            double m_R = BgrValue.Val2;
            X = m_R * a[0] + m_G * a[1] + m_B * a[2]
                + a[3] * m_R * m_G + a[4] * m_R * m_B + a[5] * m_G * m_B
                + a[6] * m_R * m_R + a[7] * m_G * m_G + a[8] * m_B * m_B
                + a[9] * m_R * m_R * m_B + a[10] * m_R * m_R * m_G
                + a[11] * m_G * m_G * m_R + a[12] * m_G * m_G * m_B
                + a[13] * m_B * m_B * m_R + a[14] * m_B * m_B * m_G
                + a[15] * m_R * m_G * m_B
                + a[16];
            Y = m_R * b[0] + m_G * b[1] + m_B * b[2]
                + b[3] * m_R * m_G + b[4] * m_R * m_B + b[5] * m_G * m_B
                + b[6] * m_R * m_R + b[7] * m_G * m_G + b[8] * m_B * m_B
                + b[9] * m_R * m_R * m_B + b[10] * m_R * m_R * m_G
                + b[11] * m_G * m_G * m_R + b[12] * m_G * m_G * m_B
                + b[13] * m_B * m_B * m_R + b[14] * m_B * m_B * m_G
                + b[15] * m_R * m_G * m_B
                + b[16];
            Z = m_R * c[0] + m_G * c[1] + m_B * c[2]
                + c[3] * m_R * m_G + c[4] * m_R * m_B + c[5] * m_G * m_B
                + c[6] * m_R * m_R + c[7] * m_G * m_G + c[8] * m_B * m_B
                + c[9] * m_R * m_R * m_B + c[10] * m_R * m_R * m_G
                + c[11] * m_G * m_G * m_R + c[12] * m_G * m_G * m_B
                + c[13] * m_B * m_B * m_R + c[14] * m_B * m_B * m_G
                + c[15] * m_R * m_G * m_B
                + c[16];
            if (X < 0) X = 0;
            if (Y < 0) Y = 0;
            if (Z < 0) Z = 0;

            // Labに変換(固定の計算式)
            // TIAS 光源 (測定値20201023)　
            // double Xn = 99.5829;
            // double Yn = 100.0;
            // double Zn = 57.1402;

            // Tangさん，竹田さんが使用してた値　(おそらく昔のTIAS光源の測定値)
            //double Xn = 102.07;
            //double Yn = 100.0;
            //double Zn = 79.41;

            // 石川さん，中口先生が使用している値　人口太陽照明？
            double Xn = 92.219;
            double Yn = 100.0;
            double Zn = 95.965;
            double cL = 116.0 * Math.Pow((Y / Yn), 1.0 / 3.0) - 16.0;
            double ca = 500.0 * (Math.Pow((X / Xn), 1.0 / 3.0) - Math.Pow((Y / Yn), 1.0 / 3.0));
            double cb = 200.0 * (Math.Pow((Y / Yn), 1.0 / 3.0) - Math.Pow((Z / Zn), 1.0 / 3.0));

            return new OpenCvSharp.Scalar(cL, ca, cb);
        }

        public void Write8colors(List<OpenCvSharp.Scalar> list_8Bgr, List<OpenCvSharp.Scalar> list_8Lab)
        {
            // 保存
            string CSVfilename = "CalculatedLab.csv";
            FileStream CSV_file = File.Open(CSVfilename, FileMode.OpenOrCreate, FileAccess.Write);
            CSV_file.Seek(0, SeekOrigin.Begin);
            CSV_file.SetLength(0);
            CSV_file.Close();

            StreamWriter CSV_data = new StreamWriter(CSVfilename);
            CSV_data.WriteLine("Area,R,G,B,L,a,B");
            for (int i = 0; i < list_8Bgr.Count(); i++)
            {
                string str = (i + 1).ToString() + ",";
                str +=
                    list_8Bgr[i].Val2.ToString("0.0000") + "," +
                    list_8Bgr[i].Val1.ToString("0.0000") + "," +
                    list_8Bgr[i].Val0.ToString("0.0000") + "," +
                    list_8Lab[i].Val0.ToString("0.0000") + "," +
                    list_8Lab[i].Val1.ToString("0.0000") + "," +
                    list_8Lab[i].Val2.ToString("0.0000");
                CSV_data.WriteLine(str);
            }
            CSV_data.Close();
        }

        private List<int> IndexOfAll(List<int> li, int target)
        {
            int num = li.IndexOf(target);
            var li_num = new List<int>();
            if (num > 0)
            {
                li_num.Add(num);
                // IndexOfメソッドで見つからなくなるまで繰り返す
                while (num > 0)
                {
                    //見つかった位置の次の位置から検索
                    num = li.IndexOf(target, num + 1);
                    if (num > 0)
                    {
                        li_num.Add(num);
                    }
                }
            }
            else
            {
                Console.WriteLine("{0}は見つかりませんでした", target);
            }
            return li_num;
        }


        public static System.Text.Encoding GetType(string FILE_NAME)
        {
            System.IO.FileStream fs = new System.IO.FileStream(FILE_NAME, System.IO.FileMode.Open, System.IO.FileAccess.Read);
            System.Text.Encoding r = GetType(fs);
            fs.Close();
            return r;
        }

        public static System.Text.Encoding GetType(System.IO.FileStream fs)
        {
            byte[] Unicode = new byte[] { 0xFF, 0xFE, 0x41 };
            byte[] UnicodeBIG = new byte[] { 0xFE, 0xFF, 0x00 };
            byte[] UTF8 = new byte[] { 0xEF, 0xBB, 0xBF };
            System.Text.Encoding reVal = System.Text.Encoding.Default;

            System.IO.BinaryReader r = new System.IO.BinaryReader(fs, System.Text.Encoding.Default);
            int i;
            int.TryParse(fs.Length.ToString(), out i);
            byte[] ss = r.ReadBytes(i);
            if (IsUTF8Bytes(ss) || (ss[0] == 0xEF && ss[1] == 0xBB && ss[2] == 0xBF))
            {
                reVal = System.Text.Encoding.UTF8;
            }
            else if (ss[0] == 0xFE && ss[1] == 0xFF && ss[2] == 0x00)
            {
                reVal = System.Text.Encoding.BigEndianUnicode;
            }
            else if (ss[0] == 0xFF && ss[1] == 0xFE && ss[2] == 0x41)
            {
                reVal = System.Text.Encoding.Unicode;
            }
            r.Close();
            return reVal;
        }

        private static bool IsUTF8Bytes(byte[] data)
        {
            int charByteCounter = 1;
            byte curByte;
            for (int i = 0; i < data.Length; i++)
            {
                curByte = data[i];
                if (charByteCounter == 1)
                {
                    if (curByte >= 0x80)
                    {
                        while (((curByte <<= 1) & 0x80) != 0)
                        {
                            charByteCounter++;
                        }
                        if (charByteCounter == 1 || charByteCounter > 6)
                        {
                            return false;
                        }
                    }
                }
                else
                {
                    if ((curByte & 0xC0) != 0x80)
                    {
                        return false;
                    }
                    charByteCounter--;
                }
            }
            if (charByteCounter > 1)
            {
                throw new Exception("Error");
            }
            return true;
        }
    }
}
