#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