diff --git a/TIASshot/Cameras/CameraBase.cs b/TIASshot/Cameras/CameraBase.cs index 3bb12d5..0e92a2a 100644 --- a/TIASshot/Cameras/CameraBase.cs +++ b/TIASshot/Cameras/CameraBase.cs @@ -37,7 +37,6 @@ protected int _bmpIndex = 0; protected bool _isPreview = false; protected List _chartMasks = new List(); - protected Dictionary _convRGB2SRGB = new Dictionary(); protected string _saveFolder = ""; protected List _shots = new List(); protected Dictionary _ShotInfo = new Dictionary(); // 設定値保存用 @@ -51,48 +50,22 @@ readonly Point2f[] PointsDst41 = new Point2f[]{ new Point2f(345, 1200), new Point2f(465, 1200), new Point2f(465, 1320), new Point2f(345, 1320), }; - readonly int[][] ExtendChannels = new int[][] - { - new int[] { 3, 3, 3 }, // 1 - new int[] { 0, 3, 3 }, // R - new int[] { 1, 3, 3 }, // G - new int[] { 2, 3, 3 }, // B - new int[] { 0, 1, 3 }, // RG - new int[] { 0, 2, 3 }, // RB - new int[] { 1, 2, 3 }, // GB - new int[] { 0, 0, 3 }, // RR - new int[] { 1, 1, 3 }, // GG - new int[] { 2, 2, 3 }, // BB - new int[] { 0, 0, 2 }, // RRB - new int[] { 0, 0, 1 }, // RRG - new int[] { 1, 1, 0 }, // GGR - new int[] { 1, 1, 2 }, // GGB - new int[] { 2, 2, 0 }, // BBR - new int[] { 2, 2, 1 }, // BBG - new int[] { 0, 1, 2 }, // RGB - }; - readonly int[] ChannelList; // 処理するチャネル数 - readonly Mat TCC_SRGB; - readonly Mat TCC_XYZ; readonly float UpdateRate; int _detectionCount = 0; Point2f _lastPosition = new Point2f(0, 0); + // 色補正処理(TCC 変換行列の計算・色変換) + readonly ColorCorrector _colorCorrector; + /// /// カメラの基本クラス /// public CameraBase(Form1 form) { _form = form; UpdateRate = Config.GetFloat("Calib/UpdateRate"); - TCC_SRGB = LoadMatFromCsv(Config.GetString("File/TccSrgbRef")); - TCC_XYZ = LoadMatFromCsv(Config.GetString("File/TccXyzRef")); - ChannelList = Config.GetString("Calib/ConversionChannels") - .Split(',') - .Select(x => int.Parse(x.Trim())) - .Where(x => x >= 4 && x <= 17) - .Distinct() - .OrderBy(x => x) - .ToArray(); + var tccSrgb = LoadMatFromCsv(Config.GetString("File/TccSrgbRef")); + var tccXyz = LoadMatFromCsv(Config.GetString("File/TccXyzRef")); + _colorCorrector = new ColorCorrector(tccSrgb, tccXyz); } /// @@ -105,11 +78,11 @@ ErrorMsg = "設定ファイル(Config.xml)の読み込みに失敗しました.\r\n終了します."; return false; } - if (TCC_SRGB is null) { + if (!_colorCorrector.SrgbRefLoaded) { ErrorMsg = $"ファイル({Config.GetString("File/TccSrgbRef")})の読み込みに失敗しました.\r\n終了します."; return false; } - if (TCC_XYZ is null) { + if (!_colorCorrector.XyzRefLoaded) { ErrorMsg = $"ファイル({Config.GetString("File/TccXyzRef")})の読み込みに失敗しました.\r\n終了します."; return false; } @@ -152,8 +125,8 @@ filename = filename.Replace("{NO}", $"{idx + 1:0000}"); Cv2.ImWrite(Path.Combine(_saveFolder, filename), img); - foreach (var channel in ChannelList) { - using (var converted = ConvertImage(img, _convRGB2SRGB[channel])) { + foreach (var channel in _colorCorrector.Channels) { + using (var converted = _colorCorrector.ConvertImage(img, channel)) { filename = GetFilenameWithChannel("File/SrgbImage", channel); filename = filename.Replace("{NO}", $"{idx + 1:0000}"); Cv2.ImWrite(Path.Combine(_saveFolder, filename), converted); @@ -280,38 +253,9 @@ SetInfo("校正データ", _saveFolder); Cv2.ImWrite(Path.Combine(_saveFolder, Config.GetString("File/TccImage")), img); Cv2.ImWrite(Path.Combine(_saveFolder, Config.GetString("File/TccRoisImage")), imgRois); - SaveMatToCsv(Path.Combine(_saveFolder, Config.GetString("File/TccRgbValues")), tccRgb); - _convRGB2SRGB.Clear(); - foreach (var channel in ChannelList) { - var convRGB2SRGB = CalcConvertMatrix(tccRgb, TCC_SRGB, channel); - SaveMatToCsv(Path.Combine(_saveFolder, GetFilenameWithChannel("File/ConvRgb2Srgb", channel)), convRGB2SRGB); - - var convRGB2XYZ = CalcConvertMatrix(tccRgb, TCC_XYZ, channel); - SaveMatToCsv(Path.Combine(_saveFolder, GetFilenameWithChannel("File/ConvRgb2Xyz", channel)), convRGB2XYZ); - if (channel == 17) { - var convOld = new Mat(convRGB2XYZ.Size(), MatType.CV_64FC1); - for (var row = 0; row < convRGB2XYZ.Rows; row++) { - var rowFrom = (row + 1) % convRGB2XYZ.Rows; // 1行ずらす - for (var col = 0; col < convRGB2XYZ.Cols; col++) { - convOld.At(row, col) = convRGB2XYZ.At(rowFrom, col); - } - } - SaveMatToCsv(Path.Combine(_saveFolder, GetFilenameWithChannel("File/ConvRgb2XyzOld", channel)), convOld); - } - - var tccSrgb = ConvertColor(tccRgb, convRGB2SRGB); - SaveMatToCsv(Path.Combine(_saveFolder, GetFilenameWithChannel("File/TccSrgbValues", channel)), tccSrgb); - - var convSrgb2Xyz = CalcConvertMatrix(tccSrgb, TCC_XYZ, channel); - SaveMatToCsv(Path.Combine(_saveFolder, GetFilenameWithChannel("File/ConvSrgb2Xyz", channel)), convSrgb2Xyz); - - // 変換精度検証 - var diff = Math.Sqrt(Cv2.Norm(TCC_SRGB, tccSrgb, NormTypes.L2)); - Debug.WriteLine($"{channel}次元 RGB→SRGB 変換行列の誤差 = {diff:.000}"); - - _convRGB2SRGB.Add(channel, convRGB2SRGB); - } + // 変換行列の計算・保存は ColorCorrector に委譲する + _colorCorrector.CalcTcc(tccRgb, _saveFolder, SaveMatToCsv); EventSound(Config.GetString("Sound/CalibDone")); _form.ShowMessage("自動校正完了"); @@ -349,87 +293,6 @@ } /// - /// 変換行列算出 - /// - /// 画像 - /// 変換目標の24x3行列 - private Mat CalcConvertMatrix(Mat src, Mat target, int channels) { - var extended = ExtendMat(src, channels); - Mat convMat = new Mat(channels, 3, MatType.CV_64FC1); - Cv2.Solve(extended, target, convMat, DecompTypes.SVD); - return convMat; - } - - /// - /// 色変換 - /// - /// - /// - /// - private Mat ConvertColor(Mat src, Mat conv) { - var extended = ExtendMat(src, conv.Rows); - var converted = (extended * conv); - return converted.ToMat(); - } - - /// - /// 8bitクリッピング処理 ForEachAsVec3d用 - /// - /// - /// - unsafe void Clip8bit(Vec3d* p, int* pos) { - p->Item0 = Math.Max(0, Math.Min(255.0, p->Item0)); - p->Item1 = Math.Max(0, Math.Min(255.0, p->Item1)); - p->Item2 = Math.Max(0, Math.Min(255.0, p->Item2)); - } - - /// - /// 画像の色変換 - /// - /// - /// - /// double型画像 - protected Mat ConvertImage(Mat src, Mat conv) { - if (src.Type() != MatType.CV_64FC3) { - src.ConvertTo(src, MatType.CV_64FC3); - } - var flatten = src.Reshape(3, src.Height * src.Width); - var extended = ExtendMat(flatten, conv.Rows); - var converted = (extended * conv).ToMat(); - var convertedImage = converted.Reshape(3, src.Height); - //Clipping to 8bit range - //unsafe { - // convertedImage.ForEachAsVec3d(Clip8bit); - //} - - var convImg8 = new Mat(); - convertedImage.ConvertTo(convImg8, MatType.CV_8UC3); - return convImg8; - } - - /// - /// 行列の拡張 3次元→高次元(指定チャンネル数) - /// - /// - /// - private Mat ExtendMat(Mat src, int channels) { - if (src.Cols * src.Channels() != 3) return src; - var dst = new Mat(src.Rows, channels, MatType.CV_64FC1); - Parallel.For(0, src.Rows, row => { - var vals = new double[] { - src.Cols == 1 ? src.At(row, 0)[2] : src.At(row, 2), // R - src.Cols == 1 ? src.At(row, 0)[1] : src.At(row, 1), // G - src.Cols == 1 ? src.At(row, 0)[0] : src.At(row, 0), // B - 1.0 - }; - for (int i = 0; i < channels; i++) { - dst.At(row, i) = vals[ExtendChannels[i][0]] * vals[ExtendChannels[i][1]] * vals[ExtendChannels[i][2]]; - } - }); - return dst; - } - - /// /// ゲイン値の更新比率計算 /// /// diff --git a/TIASshot/ColorCorrection/ColorCorrector.cs b/TIASshot/ColorCorrection/ColorCorrector.cs new file mode 100644 index 0000000..2a31d51 --- /dev/null +++ b/TIASshot/ColorCorrection/ColorCorrector.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using OpenCvSharp; + +namespace TIASshot { + /// + /// 色補正処理(TCC 変換行列の計算・色変換)を担うクラス. + /// CameraBase から委譲される.チャート検出・ファイル I/O は呼び出し側に委ねる. + /// + internal class ColorCorrector { + + // 参照値(変換目標) + readonly Mat TCC_SRGB; + readonly Mat TCC_XYZ; + + // 処理するチャネル数 + readonly int[] ChannelList; + + // 計算済み RGB→sRGB 変換行列(チャネル数ごと) + readonly Dictionary _convRGB2SRGB = new Dictionary(); + + readonly int[][] ExtendChannels = new int[][] + { + new int[] { 3, 3, 3 }, // 1 + new int[] { 0, 3, 3 }, // R + new int[] { 1, 3, 3 }, // G + new int[] { 2, 3, 3 }, // B + new int[] { 0, 1, 3 }, // RG + new int[] { 0, 2, 3 }, // RB + new int[] { 1, 2, 3 }, // GB + new int[] { 0, 0, 3 }, // RR + new int[] { 1, 1, 3 }, // GG + new int[] { 2, 2, 3 }, // BB + new int[] { 0, 0, 2 }, // RRB + new int[] { 0, 0, 1 }, // RRG + new int[] { 1, 1, 0 }, // GGR + new int[] { 1, 1, 2 }, // GGB + new int[] { 2, 2, 0 }, // BBR + new int[] { 2, 2, 1 }, // BBG + new int[] { 0, 1, 2 }, // RGB + }; + + /// + /// 色補正クラス + /// + /// sRGB 変換の参照値(24x3) + /// XYZ 変換の参照値(24x3) + public ColorCorrector(Mat tccSrgb, Mat tccXyz) { + TCC_SRGB = tccSrgb; + TCC_XYZ = tccXyz; + ChannelList = Config.GetString("Calib/ConversionChannels") + .Split(',') + .Select(x => int.Parse(x.Trim())) + .Where(x => x >= 4 && x <= 17) + .Distinct() + .OrderBy(x => x) + .ToArray(); + } + + /// sRGB 参照値が読み込めているか + public bool SrgbRefLoaded => TCC_SRGB != null; + + /// XYZ 参照値が読み込めているか + public bool XyzRefLoaded => TCC_XYZ != null; + + /// 処理対象のチャネル数一覧 + public IEnumerable Channels => ChannelList; + + /// + /// チャートの RGB 値に基づいて変換行列を計算し,CSV に保存する. + /// + /// チャート 24 色票の RGB 値(24x3) + /// 保存先フォルダ + /// Mat を CSV へ保存する処理(呼び出し側が提供) + public void CalcTcc(Mat tccRgb, string saveFolder, Action saveMatToCsv) { + + saveMatToCsv(Path.Combine(saveFolder, Config.GetString("File/TccRgbValues")), tccRgb); + + _convRGB2SRGB.Clear(); + foreach (var channel in ChannelList) { + var convRGB2SRGB = CalcConvertMatrix(tccRgb, TCC_SRGB, channel); + saveMatToCsv(Path.Combine(saveFolder, GetFilenameWithChannel("File/ConvRgb2Srgb", channel)), convRGB2SRGB); + + var convRGB2XYZ = CalcConvertMatrix(tccRgb, TCC_XYZ, channel); + saveMatToCsv(Path.Combine(saveFolder, GetFilenameWithChannel("File/ConvRgb2Xyz", channel)), convRGB2XYZ); + if (channel == 17) { + var convOld = new Mat(convRGB2XYZ.Size(), MatType.CV_64FC1); + for (var row = 0; row < convRGB2XYZ.Rows; row++) { + var rowFrom = (row + 1) % convRGB2XYZ.Rows; // 1行ずらす + for (var col = 0; col < convRGB2XYZ.Cols; col++) { + convOld.At(row, col) = convRGB2XYZ.At(rowFrom, col); + } + } + saveMatToCsv(Path.Combine(saveFolder, GetFilenameWithChannel("File/ConvRgb2XyzOld", channel)), convOld); + } + + var tccSrgb = ConvertColor(tccRgb, convRGB2SRGB); + saveMatToCsv(Path.Combine(saveFolder, GetFilenameWithChannel("File/TccSrgbValues", channel)), tccSrgb); + + var convSrgb2Xyz = CalcConvertMatrix(tccSrgb, TCC_XYZ, channel); + saveMatToCsv(Path.Combine(saveFolder, GetFilenameWithChannel("File/ConvSrgb2Xyz", channel)), convSrgb2Xyz); + + // 変換精度検証 + var diff = Math.Sqrt(Cv2.Norm(TCC_SRGB, tccSrgb, NormTypes.L2)); + Debug.WriteLine($"{channel}次元 RGB→SRGB 変換行列の誤差 = {diff:.000}"); + + _convRGB2SRGB.Add(channel, convRGB2SRGB); + } + } + + /// + /// 指定チャネル数の変換行列で画像を色変換する. + /// + /// + /// + /// 8bit 画像 + public Mat ConvertImage(Mat src, int channel) { + return ConvertImage(src, _convRGB2SRGB[channel]); + } + + /// + /// ファイル名にチャネル数を追加する + /// + /// + /// + /// + private string GetFilenameWithChannel(string config, int channel) { + var filename = Config.GetString(config); + filename = filename.Replace("{CN}", $"{channel:00}"); + return filename; + } + + /// + /// 変換行列算出 + /// + /// 画像 + /// 変換目標の24x3行列 + private Mat CalcConvertMatrix(Mat src, Mat target, int channels) { + var extended = ExtendMat(src, channels); + Mat convMat = new Mat(channels, 3, MatType.CV_64FC1); + Cv2.Solve(extended, target, convMat, DecompTypes.SVD); + return convMat; + } + + /// + /// 色変換 + /// + /// + /// + /// + private Mat ConvertColor(Mat src, Mat conv) { + var extended = ExtendMat(src, conv.Rows); + var converted = (extended * conv); + return converted.ToMat(); + } + + /// + /// 8bitクリッピング処理 ForEachAsVec3d用 + /// + /// + /// + unsafe void Clip8bit(Vec3d* p, int* pos) { + p->Item0 = Math.Max(0, Math.Min(255.0, p->Item0)); + p->Item1 = Math.Max(0, Math.Min(255.0, p->Item1)); + p->Item2 = Math.Max(0, Math.Min(255.0, p->Item2)); + } + + /// + /// 画像の色変換 + /// + /// + /// + /// double型画像 + private Mat ConvertImage(Mat src, Mat conv) { + if (src.Type() != MatType.CV_64FC3) { + src.ConvertTo(src, MatType.CV_64FC3); + } + var flatten = src.Reshape(3, src.Height * src.Width); + var extended = ExtendMat(flatten, conv.Rows); + var converted = (extended * conv).ToMat(); + var convertedImage = converted.Reshape(3, src.Height); + //Clipping to 8bit range + //unsafe { + // convertedImage.ForEachAsVec3d(Clip8bit); + //} + + var convImg8 = new Mat(); + convertedImage.ConvertTo(convImg8, MatType.CV_8UC3); + return convImg8; + } + + /// + /// 行列の拡張 3次元→高次元(指定チャンネル数) + /// + /// + /// + private Mat ExtendMat(Mat src, int channels) { + if (src.Cols * src.Channels() != 3) return src; + var dst = new Mat(src.Rows, channels, MatType.CV_64FC1); + Parallel.For(0, src.Rows, row => { + var vals = new double[] { + src.Cols == 1 ? src.At(row, 0)[2] : src.At(row, 2), // R + src.Cols == 1 ? src.At(row, 0)[1] : src.At(row, 1), // G + src.Cols == 1 ? src.At(row, 0)[0] : src.At(row, 0), // B + 1.0 + }; + for (int i = 0; i < channels; i++) { + dst.At(row, i) = vals[ExtendChannels[i][0]] * vals[ExtendChannels[i][1]] * vals[ExtendChannels[i][2]]; + } + }); + return dst; + } + } +} diff --git a/TIASshot/TIASshot.csproj b/TIASshot/TIASshot.csproj index e7225b1..e52dceb 100644 --- a/TIASshot/TIASshot.csproj +++ b/TIASshot/TIASshot.csproj @@ -119,6 +119,7 @@ + Form diff --git "a/docs/03_PLAN/PLAN_01_\343\203\252\343\203\225\343\202\241\343\202\257\343\202\277\343\203\252\343\203\263\343\202\260\350\250\210\347\224\273.md" "b/docs/03_PLAN/PLAN_01_\343\203\252\343\203\225\343\202\241\343\202\257\343\202\277\343\203\252\343\203\263\343\202\260\350\250\210\347\224\273.md" index d8464c1..0d25896 100644 --- "a/docs/03_PLAN/PLAN_01_\343\203\252\343\203\225\343\202\241\343\202\257\343\202\277\343\203\252\343\203\263\343\202\260\350\250\210\347\224\273.md" +++ "b/docs/03_PLAN/PLAN_01_\343\203\252\343\203\225\343\202\241\343\202\257\343\202\277\343\203\252\343\203\263\343\202\260\350\250\210\347\224\273.md" @@ -52,10 +52,11 @@ - `ColorCorrection/` フォルダを作成する. - CameraBase から以下を新クラス `ColorCorrector` に切り出す. - - フィールド: `TCC_SRGB`, `TCC_XYZ`, `ChannelList`, `ExtendChannels`, `UpdateRate` + - フィールド: `TCC_SRGB`, `TCC_XYZ`, `ChannelList`, `ExtendChannels` - メソッド: `ExtendMat`, `CalcConvertMatrix`, `ConvertColor`, `ConvertImage` - メソッド: `CalcTcc`(チャート RGB 値の受け取りと行列計算・保存を担当) - CameraBase は `ColorCorrector` を保持し,処理を委譲する. +- ※ `UpdateRate` はゲイン/ホワイトバランス補正(`GetRatio`)専用で色補正とは別関心事のため,`CameraBase` に残す(当初案から変更). **テスト**