Newer
Older
TIASShot / TIASshot / SelfTest / SelfTestChart.cs
#if SELFTEST
using System;
using OpenCvSharp;
using OpenCvSharp.Aruco;

namespace TIASshot {

    /// <summary>
    /// セルフテスト用の合成舌診チャート(SELFTEST 限定).実画像を使わず,ChartDetector の
    /// チャート座標系(810x1545,ArUco Dict4X4_50 の ID 40(上)/ 41(下),24 色票)どおりに描く.
    /// 色票の色は TCC の sRGB 参照値(tcc_srgb.csv)をチャネルごとの 1 次式で「カメラ色」に歪めたもの.
    /// 歪みが 1 次式なので,校正で求まる変換行列を掛ければ参照値へほぼ戻る(色変換の検算に使える).
    /// </summary>
    internal static class SelfTestChart {

        // ChartDetector のチャート座標系
        public const int ChartWidth = 810;
        public const int ChartHeight = 1545;
        private const int MarkerSize = 120;                // PointsDst40/41 の一辺(345→465)
        private static readonly Point Marker40 = new Point(345, 130);
        private static readonly Point Marker41 = new Point(345, 1200);
        private const int PatchRoiSize = 80;               // ChartDetector のマスク一辺(マーカー 2 個検出時)
        private const int PatchMargin = 15;                // ROI の外側に描く余白(検出誤差の吸収)

        // フレーム内でのチャートの配置(カメラ画像の中にチャートが写っている状況を模す)
        public const int FrameWidth = 960;
        public const int FrameHeight = 1700;
        public const int OffsetX = 75;
        public const int OffsetY = 78;

        // 「カメラ色」への歪み(チャネルごとの 1 次式 v' = a*v + b.B, G, R の順)
        private static readonly double[] DistortA = { 0.72, 0.80, 0.76 };
        private static readonly double[] DistortB = { 12.0, 8.0, 10.0 };

        /// <summary>
        /// i 番目の色票 ROI(ChartDetector と同じ式)のフレーム座標を返す.
        /// </summary>
        public static Rect PatchRoi(int i) {
            var row = i % 6;
            var col = i / 6;
            var x = 581 - col * 144;
            var y = 318 + row * 144;
            return new Rect(OffsetX + x, OffsetY + y, PatchRoiSize, PatchRoiSize);
        }

        /// <summary>
        /// 合成チャートを写したフレーム(CV_8UC3・BGR)を生成する.戻り値の所有権は呼び出し側.
        /// </summary>
        /// <param name="tccSrgb">TCC の sRGB 参照値(24x3・B,G,R 列)</param>
        public static Mat CreateChartFrame(Mat tccSrgb) {
            var frame = new Mat(FrameHeight, FrameWidth, MatType.CV_8UC3, new Scalar(60, 60, 60));
            try {
                // チャート台紙
                Cv2.Rectangle(frame, new Rect(OffsetX, OffsetY, ChartWidth, ChartHeight),
                    new Scalar(200, 200, 200), Cv2.FILLED);

                // ArUco マーカー(ID 40 が上,ID 41 が下)
                DrawMarker(frame, 40, Marker40);
                DrawMarker(frame, 41, Marker41);

                // 24 色票(ROI より一回り大きく塗る)
                for (var i = 0; i < 24; i++) {
                    var roi = PatchRoi(i);
                    var patch = new Rect(roi.X - PatchMargin, roi.Y - PatchMargin,
                        PatchRoiSize + PatchMargin * 2, PatchRoiSize + PatchMargin * 2);
                    Cv2.Rectangle(frame, patch, CameraColor(tccSrgb, i), Cv2.FILLED);
                }
                return frame;
            } catch {
                frame.Dispose();
                throw;
            }
        }

        /// <summary>i 番目の色票の「カメラ色」(参照値を 1 次式で歪めた BGR)</summary>
        public static Scalar CameraColor(Mat tccSrgb, int i) {
            var v = new double[3];
            for (var c = 0; c < 3; c++) {
                var ref_ = tccSrgb.At<double>(i, c);
                v[c] = Math.Max(0, Math.Min(255, DistortA[c] * ref_ + DistortB[c]));
            }
            return new Scalar(v[0], v[1], v[2]);
        }

        private static void DrawMarker(Mat frame, int id, Point chartPos) {
            using (var dict = CvAruco.GetPredefinedDictionary(PredefinedDictionaryType.Dict4X4_50))
            using (var marker = new Mat())
            using (var markerBgr = new Mat()) {
                dict.GenerateImageMarker(id, MarkerSize, marker, 1);
                Cv2.CvtColor(marker, markerBgr, ColorConversionCodes.GRAY2BGR);
                using (var dst = new Mat(frame, new Rect(OffsetX + chartPos.X, OffsetY + chartPos.Y, MarkerSize, MarkerSize))) {
                    markerBgr.CopyTo(dst);
                }
            }
        }

        /// <summary>
        /// プレビュー確認用の単色フレーム(CV_8UC3・BGR)を生成する.フレーム番号で色を変え,
        /// 表示されたのがどのフレームのコピーかを画素値で判別できるようにする.
        /// </summary>
        public static Mat CreatePlainFrame(int index, int width, int height) {
            return new Mat(height, width, MatType.CV_8UC3, PlainFrameColor(index));
        }

        /// <summary>CreatePlainFrame の index 番目の色(B, G, R)</summary>
        public static Scalar PlainFrameColor(int index) {
            return new Scalar(20 + (index * 10) % 200, 100, 220 - (index * 10) % 200);
        }
    }
}
#endif