Newer
Older
TIASShot / TIASshot / ChartDetection / ChartDetector.cs
using System;
using System.Collections.Generic;
using OpenCvSharp;
using OpenCvSharp.Aruco;

namespace TIASshot {
    /// <summary>
    /// 舌診チャートの検出(ARマーカー検出・固定判定・ホモグラフィ計算・マスク生成)を担うクラス.
    /// CameraBase から委譲される.フォーム通知は呼び出し側が提供する.
    /// </summary>
    internal class ChartDetector {

        readonly Dictionary ARDict = CvAruco.GetPredefinedDictionary(PredefinedDictionaryName.Dict4X4_50);
        readonly Point2f[] PointsDst40 = new Point2f[] {
                new Point2f(345, 130),new Point2f(465, 130),new Point2f(465, 250),new Point2f(345, 250),
            };
        readonly Point2f[] PointsDst41 = new Point2f[]{
                new Point2f(345, 1200), new Point2f(465, 1200), new Point2f(465, 1320), new Point2f(345, 1320),
            };
        int _detectionCount = 0;
        Point2f _lastPosition = new Point2f(0, 0);

        // 状態表示(呼び出し側が提供)
        readonly Action<string> _showMessage;

        public ChartDetector(Action<string> showMessage) {
            _showMessage = showMessage;
        }

        /// <summary>
        /// チャートの検出.固定が確認できた場合のみチャートマスク(24 枚)を返す.
        /// 未検出・上下逆・未固定の場合は null を返す.
        /// </summary>
        /// <param name="img"></param>
        /// <returns>チャートマスクリスト,または null</returns>
        public List<Mat> DetectChart(Mat img) {

            // ARマーカー検出
            CvAruco.DetectMarkers(img, ARDict, out var corners, out var ids,
                 new DetectorParameters(), out var rejectedImgPoints);
            if (ids.Length < 1) return null;

            // マーカー座標格納
            var ptsPict = new List<Point2f>();
            var ptsModel = new List<Point2f>();
            Point2f position = new Point2f();
            float y40 = 0, y41 = 0;
            for (int i = 0; i < ids.Length; i++) {
                if (ids[i] == 40) {
                    ptsPict.AddRange(corners[i]);
                    ptsModel.AddRange(PointsDst40);
                    position = corners[i][3];
                    y40 = corners[i][3].Y;
                }
                if (ids[i] == 41) {
                    ptsPict.AddRange(corners[i]);
                    ptsModel.AddRange(PointsDst41);
                    y41 = corners[i][3].Y;
                }
            }
            if (ptsPict.Count < 8) return null;
            if (y40 > y41) {
                _showMessage("舌診チャートが上下逆方向です");
                return null;
            }

            // チャートの固定判定
            _showMessage("舌診チャートの検出中");
            var dist = (float)position.DistanceTo(_lastPosition);
            if (dist < Config.GetFloat("Calib/ChartSetCriteria")) {
                _detectionCount++;
            } else {
                _detectionCount = 0;
            }
            _lastPosition = position;
            if (_detectionCount < Config.GetInt("Calib/ChartSetCount")) return null;

            // ホモグラフィの計算
            var matPtsPict = Mat.FromArray(ptsPict);
            var matPtsModel = Mat.FromArray(ptsModel);
            var matH = Cv2.FindHomography(matPtsModel, matPtsPict);
            var imgF = new Mat(1545, 810, MatType.CV_8UC3);
            Cv2.WarpPerspective(img, imgF, matH, imgF.Size());

            // チャートマスク作成
            var chartMasks = new List<Mat>();
            var roiSize = ptsPict.Count < 8 ? 60 : 80;
            for (int i = 0; i < 24; i++) {
                var row = i % 6;
                var col = i / 6;
                var x = 581 - col * 144 + (ptsPict.Count < 8 ? 10 : 0);
                var y = 318 + row * 144 + (ptsPict.Count < 8 ? 10 : 0);
                var roi = new Rect(x, y, roiSize, roiSize);
                using (var mask = new Mat(1545, 810, MatType.CV_8U)) {
                    Cv2.Rectangle(mask, roi, new Scalar(255), Cv2.FILLED);
                    var maskF = new Mat(img.Size(), MatType.CV_8U);
                    Cv2.WarpPerspective(mask, maskF, matH, maskF.Size());
                    chartMasks.Add(maskF);
                }
            }

            _showMessage("舌診チャート検出 校正中");
            return chartMasks;
        }
    }
}