/// <summary>
        /// 画像の射影変換
        /// </summary>
        /// <param name="sourceImage">元の画像の2次元配列</param>
        /// <param name="sourcePosList">元の画像の対応点のリスト</param>
        /// <param name="destPosList">変換後の画像の対応点のリスト</param>
        /// <returns>結果の画像の3次元配列</returns>
        public static byte[,,] ApplyProjectiveTransformation(
            byte[,,] sourceImage, List <Point> sourcePosList, List <Point> destPosList)
        {
            int imageWidth    = sourceImage.GetLength(0);
            int imageHeight   = sourceImage.GetLength(1);
            int numOfChannels = sourceImage.GetLength(2);

            // 結果画像から元の画像への射影変換を計算
            Matrix <double> matrixProj = ImageMosaicing.DestToSourceProjectionMatrix(
                sourcePosList, destPosList);

            int destWidth  = destPosList.Select(pos => pos.X).Max();
            int destHeight = destPosList.Select(pos => pos.Y).Max();

            byte[,,] destImage = new byte[destWidth, destHeight, numOfChannels];

            // 結果の画像を生成
            Vector <double> destPos = Vector <double> .Build.Dense(3);

            Vector <double> sourcePos = Vector <double> .Build.Dense(3);

            int sourcePosX = 0;
            int sourcePosY = 0;

            for (int x = 0; x < destWidth; ++x)
            {
                for (int y = 0; y < destHeight; ++y)
                {
                    destPos[0] = x;
                    destPos[1] = y;
                    destPos[2] = 1.0;

                    // 元の画像の対応点を計算
                    sourcePos = matrixProj * destPos;

                    // 出力画像の画素値を決定
                    sourcePosX = (int)Math.Floor(sourcePos[0] / sourcePos[2] + 0.5);
                    sourcePosY = (int)Math.Floor(sourcePos[1] / sourcePos[2] + 0.5);

                    if (sourcePosX < 0 || sourcePosX >= imageWidth ||
                        sourcePosY < 0 || sourcePosY >= imageHeight)
                    {
                        destImage[x, y, 0] = 255;
                        destImage[x, y, 1] = 255;
                        destImage[x, y, 2] = 255;
                    }
                    else
                    {
                        destImage[x, y, 0] = sourceImage[sourcePosX, sourcePosY, 0];
                        destImage[x, y, 1] = sourceImage[sourcePosX, sourcePosY, 1];
                        destImage[x, y, 2] = sourceImage[sourcePosX, sourcePosY, 2];
                    }
                }
            }

            return(destImage);
        }
        /// <summary>
        /// 基準画像と別の画像を結合
        /// </summary>
        /// <param name="baseImage">基準画像の3次元配列</param>
        /// <param name="otherImages">画像の3次元配列のリスト</param>
        /// <param name="baseImagePosList">画像ごとに対応点をまとめたリスト</param>
        /// <param name="otherImagePosList">画像ごとに対応点をまとめたリスト</param>
        /// <returns>結果の画像の3次元配列</returns>
        public static byte[,,] CombineImages(
            byte[,,] baseImage, List <byte[, , ]> otherImages,
            List <List <Point> > baseImagePosList, List <List <Point> > otherImagePosList)
        {
            List <Point> diffList          = new List <Point>();
            int          imageWidth        = baseImage.GetLength(0);
            int          imageHeight       = baseImage.GetLength(1);
            int          numOfChannels     = baseImage.GetLength(2);
            int          baseImageWidth    = baseImage.GetLength(0);
            int          baseImageHeight   = baseImage.GetLength(1);
            int          baseImageTopLeftX = 0;
            int          baseImageTopLeftY = 0;

            Debug.Assert(otherImages.Count() == baseImagePosList.Count());
            Debug.Assert(baseImagePosList.Count() == otherImagePosList.Count());

            // 他の入力画像を基準画像に変換
            for (int i = 0; i < otherImages.Count(); ++i)
            {
                // 他の入力画像から基準画像への射影変換を計算
                Matrix <double> matrixProj = ImageMosaicing.DestToSourceProjectionMatrix(
                    otherImagePosList[i], baseImagePosList[i]);

                // 他の入力画像から基準画像への射影変換を実行
                otherImages[i] = ImageMosaicing.ApplyProjectiveTransformation2(
                    otherImages[i], matrixProj, out int diffX, out int diffY);

                // 基準画像の原点に対応する, 補助画像上の点の座標を保存
                diffList.Add(new Point(diffX, diffY));

                // 基準画像の原点に対応する, 結果の画像の座標を更新
                if (diffX > 0)
                {
                    baseImageTopLeftX = Math.Max(baseImageTopLeftX, diffX);
                }
                if (diffY > 0)
                {
                    baseImageTopLeftY = Math.Max(baseImageTopLeftY, diffY);
                }

                // 結果の画像の大きさを更新
                if (diffX < 0)
                {
                    imageWidth = Math.Max(baseImageWidth, otherImages[i].GetLength(0) - diffX);
                }
                if (diffY < 0)
                {
                    imageHeight = Math.Max(baseImageHeight, otherImages[i].GetLength(1) - diffY);
                }
            }

            // 結果の画像の大きさを更新
            imageWidth  += baseImageTopLeftX;
            imageHeight += baseImageTopLeftY;

            // 結果の画像を白で埋める
            byte[,,] destImage = new byte[imageWidth, imageHeight, numOfChannels];

            for (int x = 0; x < imageWidth; ++x)
            {
                for (int y = 0; y < imageHeight; ++y)
                {
                    destImage[x, y, 0] = 255;
                    destImage[x, y, 1] = 255;
                    destImage[x, y, 2] = 255;
                }
            }

            int actualX;
            int actualY;

            // 結果の画像に補助画像を埋める
            for (int i = 0; i < otherImages.Count(); ++i)
            {
                for (int x = 0; x < otherImages[i].GetLength(0); ++x)
                {
                    for (int y = 0; y < otherImages[i].GetLength(1); ++y)
                    {
                        // 結果の画像における座標を計算
                        actualX = baseImageTopLeftX - diffList[i].X + x;
                        actualY = baseImageTopLeftY - diffList[i].Y + y;

                        if (actualX < 0 || actualX >= imageWidth ||
                            actualY < 0 || actualY >= imageHeight)
                        {
                            continue;
                        }

                        destImage[actualX, actualY, 0] = otherImages[i][x, y, 0];
                        destImage[actualX, actualY, 1] = otherImages[i][x, y, 1];
                        destImage[actualX, actualY, 2] = otherImages[i][x, y, 2];
                    }
                }
            }

            // 結果の画像に基準画像を埋める
            for (int x = 0; x < baseImage.GetLength(0); ++x)
            {
                for (int y = 0; y < baseImage.GetLength(1); ++y)
                {
                    // 結果の画像における座標を計算
                    actualX = baseImageTopLeftX + x;
                    actualY = baseImageTopLeftY + y;

                    if (actualX < 0 || actualX >= imageWidth ||
                        actualY < 0 || actualY >= imageHeight)
                    {
                        continue;
                    }

                    destImage[actualX, actualY, 0] = baseImage[x, y, 0];
                    destImage[actualX, actualY, 1] = baseImage[x, y, 1];
                    destImage[actualX, actualY, 2] = baseImage[x, y, 2];
                }
            }

            return(destImage);
        }
        /// <summary>
        /// 画像から障害物を除去
        /// </summary>
        /// <param name="baseImage">基準画像の3次元配列</param>
        /// <param name="otherImages">障害物を除去するために使用する補助画像</param>
        /// <param name="baseImagePosList">補助画像ごとに対応点をまとめたリスト</param>
        /// <param name="otherImagePosList">補助画像ごとに対応点をまとめたリスト</param>
        /// <param name="obstaclePosList">基準画像と補助画像における障害物の座標をまとめたリスト</param>
        /// <returns>結果の画像の3次元配列</returns>
        public static byte[,,] RemoveObstacleFromImage(
            byte[,,] baseImage, List <byte[, , ]> otherImages,
            List <List <Point> > baseImagePosList, List <List <Point> > otherImagePosList,
            List <List <Point> > obstaclePosList)
        {
            int imageWidth    = baseImage.GetLength(0);
            int imageHeight   = baseImage.GetLength(1);
            int numOfChannels = baseImage.GetLength(2);

            byte[,,] destImage = new byte[imageWidth, imageHeight, numOfChannels];
            int i;

            Debug.Assert(otherImages.Count() == baseImagePosList.Count());
            Debug.Assert(baseImagePosList.Count() == otherImagePosList.Count());

            // 他の入力画像を基準画像に変換
            for (i = 0; i < otherImages.Count(); ++i)
            {
                // 他の入力画像から基準画像への射影変換を計算
                Matrix <double> matrixProj = ImageMosaicing.DestToSourceProjectionMatrix(
                    otherImagePosList[i], baseImagePosList[i]);
                // 他の入力画像から基準画像への射影変換を実行
                otherImages[i] = ImageMosaicing.ApplyProjectiveTransformation(
                    otherImages[i], otherImagePosList[i], baseImagePosList[i]);

                // 障害物の領域を表す座標も射影行列で変換
                Vector <double> sourcePos = Vector <double> .Build.Dense(3);

                Vector <double> destPos = Vector <double> .Build.Dense(3);

                for (int j = 0; j < obstaclePosList[i + 1].Count(); ++j)
                {
                    sourcePos[0] = obstaclePosList[i + 1][j].X;
                    sourcePos[1] = obstaclePosList[i + 1][j].Y;
                    sourcePos[2] = 1.0;

                    destPos = matrixProj.Inverse() * sourcePos;

                    obstaclePosList[i + 1][j] = new Point(
                        (int)Math.Floor(destPos[0] / destPos[2] + 0.5),
                        (int)Math.Floor(destPos[1] / destPos[2] + 0.5));
                }
            }

            for (int x = 0; x < imageWidth; ++x)
            {
                for (int y = 0; y < imageHeight; ++y)
                {
                    // 基準画像の障害物領域であるとき, 他の画像で埋める
                    if (ImageMosaicing.IsPointInPolygon(x, y, obstaclePosList[0]))
                    {
                        // 他の画像の障害物領域でなければ採用
                        for (i = 0; i < otherImages.Count(); ++i)
                        {
                            if (!ImageMosaicing.IsPointInPolygon(x, y, obstaclePosList[i + 1]))
                            {
                                break;
                            }
                        }

                        if (i != otherImages.Count())
                        {
                            // 他の画像で障害物領域ではなかったので採用
                            destImage[x, y, 0] = otherImages[i][x, y, 0];
                            destImage[x, y, 1] = otherImages[i][x, y, 1];
                            destImage[x, y, 2] = otherImages[i][x, y, 2];
                        }
                        else
                        {
                            // 他の画像で障害物領域でないものが見つからなかった
                            destImage[x, y, 0] = 0;
                            destImage[x, y, 1] = 0;
                            destImage[x, y, 2] = 0;
                        }
                    }
                    else
                    {
                        // 基準画像の障害物領域ではない
                        destImage[x, y, 0] = baseImage[x, y, 0];
                        destImage[x, y, 1] = baseImage[x, y, 1];
                        destImage[x, y, 2] = baseImage[x, y, 2];
                    }
                }
            }

            return(destImage);
        }