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;
}
}
}