diff --git a/TIASshot/CameraBase.cs b/TIASshot/CameraBase.cs index 55eac5f..41b9356 100644 --- a/TIASshot/CameraBase.cs +++ b/TIASshot/CameraBase.cs @@ -5,6 +5,7 @@ using System.Drawing; using System.IO; using System.Linq; +using System.Runtime.Remoting.Channels; using System.Security.Cryptography; using System.Text; using System.Threading; @@ -25,6 +26,7 @@ public string DeviceName { get; protected set; } = "Unknown"; public string SerialNumber { get; protected set; } public string ErrorMsg { get; protected set; } + public bool IsCalibrating => _calibrating > 0; // 派生クラスで使用するメンバ protected Form1 _form; @@ -34,7 +36,7 @@ protected int _bmpIndex = 0; protected bool _isPreview = false; protected List _chartMasks = new List(); - protected Mat _convRGB2SRGB; + protected Dictionary _convRGB2SRGB = new Dictionary(); protected string _saveFolder = ""; protected List _shots = new List(); @@ -66,22 +68,29 @@ 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); - /// /// カメラの基本クラス /// public CameraBase(Form1 form) { _form = form; Config.Load(); - UpdateRate = Config.GetFloat("UpdateRate"); - TCC_SRGB = LoadMatFromCsv(Config.GetString("TccSrgbTableFile")); - TCC_XYZ = LoadMatFromCsv(Config.GetString("TccXyzTableFile")); + UpdateRate = Config.GetFloat("Calib/UpdateRate"); + TCC_SRGB = LoadMatFromCsv(Config.GetString("File/TccSrgbTable")); + TCC_XYZ = LoadMatFromCsv(Config.GetString("File/TccXyzTable")); + ChannelList = Config.GetString("Calib/ConversionChannels") + .Split(',') + .Select(x => int.Parse(x.Trim())) + .Where(x => x >= 4 && x <= 17) + .Distinct() + .OrderBy(x => x) + .ToArray(); } /// @@ -95,11 +104,11 @@ return false; } if (TCC_SRGB is null) { - ErrorMsg = $"ファイル({Config.GetString("TccSrgbTableFile")})の読み込みに失敗しました.\r\n終了します."; + ErrorMsg = $"ファイル({Config.GetString("File/TccSrgbTable")})の読み込みに失敗しました.\r\n終了します."; return false; } if (TCC_XYZ is null) { - ErrorMsg = $"ファイル({Config.GetString("TccXyzTableFile")})の読み込みに失敗しました.\r\n終了します."; + ErrorMsg = $"ファイル({Config.GetString("File/TccXyzTable")})の読み込みに失敗しました.\r\n終了します."; return false; } return true; @@ -130,19 +139,16 @@ /// protected void SaveImages(Mat img, int idx) { - var filename = Config.GetString("ImageRgbFile"); - filename = filename.Replace("0000", $"{idx + 1:0000}"); - //var px1 = img.At(0, 0); - //Debug.WriteLine($"img (0,0)={px1[0]}, {px1[1]}, {px1[2]}" ); - + var filename = Config.GetString("File/ImageRgb"); + filename = filename.Replace("{NO}", $"{idx + 1:0000}"); Cv2.ImWrite(Path.Combine(_saveFolder, filename), img); - using (var converted = ConvertImage(img, _convRGB2SRGB)) { - //var px2 = converted.At(0, 0); - //Debug.WriteLine($"conv(0,0)= {px2[0]}, {px2[1]}, {px2[2]}"); - filename = Config.GetString("ImageSrgbFile"); - filename = filename.Replace("0000", $"{idx + 1:0000}"); - Cv2.ImWrite(Path.Combine(_saveFolder, filename), converted); + foreach (var channel in ChannelList) { + using (var converted = ConvertImage(img, _convRGB2SRGB[channel])) { + filename = GetFilenameWithChannel("File/ImageSrgb", channel); + filename = filename.Replace("{NO}", $"{idx + 1:0000}"); + Cv2.ImWrite(Path.Combine(_saveFolder, filename), converted); + } } } @@ -202,13 +208,13 @@ // チャートの固定判定 _form.ShowMessage("舌診チャートの検出中"); var dist = (float)position.DistanceTo(_lastPosition); - if (dist < Config.GetFloat("ChartSetCriteria")) { + if (dist < Config.GetFloat("Calib/ChartSetCriteria")) { _detectionCount++; } else { _detectionCount = 0; } _lastPosition = position; - if (_detectionCount < Config.GetInt("ChartSetCount")) return; + if (_detectionCount < Config.GetInt("Calib/ChartSetCount")) return; // ホモグラフィの計算 var matPtsPict = Mat.FromArray(ptsPict); @@ -235,7 +241,19 @@ } _form.ShowMessage("舌診チャート検出 校正中"); - _calibrating = Config.GetInt("CalibrationFrames"); + _calibrating = Config.GetInt("Calib/Frames"); + } + + /// + /// ファイル名にチャネル数を追加する + /// + /// + /// + /// + private string GetFilenameWithChannel(string config, int channel) { + var filename = Config.GetString(config); + filename = filename.Replace("{CN}", $"{channel:00}"); + return filename; } /// @@ -243,27 +261,37 @@ /// /// protected void CalcTcc(Mat img) { + // 変換行列の計算 var tccRgb = GetTccRgb(img); var imgRois = GetTccRoisImage(img); - _convRGB2SRGB = CalcConvertMatrix(tccRgb, TCC_SRGB); - var convRGB2XYZ = CalcConvertMatrix(tccRgb, TCC_XYZ); - var tccSrgb = ConvertColor(tccRgb, _convRGB2SRGB); - var convSrgb2Xyz = CalcConvertMatrix(tccSrgb, TCC_XYZ); - - // 変換精度検証 - var diff = Math.Sqrt(Cv2.Norm(TCC_SRGB, tccSrgb, NormTypes.L2)); - Debug.WriteLine($"RGB→SRGB 変換行列の誤差 = {diff:.000}"); // データ保存 SetSaveFolder("校正"); - Cv2.ImWrite(Path.Combine(_saveFolder, Config.GetString("TccSaveFile")), img); - Cv2.ImWrite(Path.Combine(_saveFolder, Config.GetString("TccRoisFile")), imgRois); - SaveMatToCsv(Path.Combine(_saveFolder, Config.GetString("ConvSrgbSaveFile")), _convRGB2SRGB); - SaveMatToCsv(Path.Combine(_saveFolder, Config.GetString("ConvXyzSaveFile")), convRGB2XYZ); - SaveMatToCsv(Path.Combine(_saveFolder, Config.GetString("ConvSrgb2XyzSaveFile")), convSrgb2Xyz); - SaveMatToCsv(Path.Combine(_saveFolder, Config.GetString("TccRgbFile")), tccRgb); - SaveMatToCsv(Path.Combine(_saveFolder, Config.GetString("TccSrgbFile")), tccSrgb); + Cv2.ImWrite(Path.Combine(_saveFolder, Config.GetString("File/TccSave")), img); + Cv2.ImWrite(Path.Combine(_saveFolder, Config.GetString("File/TccRois")), imgRois); + SaveMatToCsv(Path.Combine(_saveFolder, Config.GetString("File/TccRgb")), tccRgb); + + _convRGB2SRGB.Clear(); + foreach (var channel in ChannelList) { + var convRGB2SRGB = CalcConvertMatrix(tccRgb, TCC_SRGB, channel); + SaveMatToCsv(Path.Combine(_saveFolder, GetFilenameWithChannel("File/ConvSrgbSave", channel)), convRGB2SRGB); + + var convRGB2XYZ = CalcConvertMatrix(tccRgb, TCC_XYZ, channel); + SaveMatToCsv(Path.Combine(_saveFolder, GetFilenameWithChannel("File/ConvXyzSave", channel)), convRGB2XYZ); + + var tccSrgb = ConvertColor(tccRgb, convRGB2SRGB); + SaveMatToCsv(Path.Combine(_saveFolder, GetFilenameWithChannel("File/TccSrgb", channel)), tccSrgb); + + var convSrgb2Xyz = CalcConvertMatrix(tccSrgb, TCC_XYZ, channel); + SaveMatToCsv(Path.Combine(_saveFolder, GetFilenameWithChannel("File/ConvSrgb2XyzSave", channel)), convSrgb2Xyz); + + // 変換精度検証 + var diff = Math.Sqrt(Cv2.Norm(TCC_SRGB, tccSrgb, NormTypes.L2)); + Debug.WriteLine($"{channel}次元 RGB→SRGB 変換行列の誤差 = {diff:.000}"); + + _convRGB2SRGB.Add(channel, convRGB2SRGB); + } _form.ShowMessage("自動校正完了"); _form.EnableShots(); @@ -304,9 +332,9 @@ /// /// 画像 /// 変換目標の24x3行列 - private Mat CalcConvertMatrix(Mat src, Mat target) { - var extended = ExtendMat(src); - Mat convMat = new Mat(17, 3, MatType.CV_64FC1); + 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; } @@ -315,11 +343,11 @@ /// 色変換 /// /// - /// + /// /// - private Mat ConvertColor(Mat src, Mat convMat) { - var extended = ExtendMat(src); - var converted = (extended * convMat); + private Mat ConvertColor(Mat src, Mat conv) { + var extended = ExtendMat(src, conv.Rows); + var converted = (extended * conv); return converted.ToMat(); } @@ -345,7 +373,7 @@ src.ConvertTo(src, MatType.CV_64FC3); } var flatten = src.Reshape(3, src.Height * src.Width); - var extended = ExtendMat(flatten); + var extended = ExtendMat(flatten, conv.Rows); var converted = (extended * conv).ToMat(); var convertedImage = converted.Reshape(3, src.Height); //Clipping to 8bit range @@ -359,11 +387,11 @@ } /// - /// 行列の拡張 3次元→17次元 + /// 行列の拡張 3次元→高次元(指定チャンネル数) /// /// /// - private Mat ExtendMat(Mat src, int channels = 17) { + 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 => { @@ -373,7 +401,7 @@ src.Cols == 1 ? src.At(row, 0)[0] : src.At(row, 0), // B 1.0 }; - for (int i = 0; i < ExtendChannels.Length; i++) { + for (int i = 0; i < channels; i++) { dst.At(row, i) = vals[ExtendChannels[i][0]] * vals[ExtendChannels[i][1]] * vals[ExtendChannels[i][2]]; } }); diff --git a/TIASshot/Config.cs b/TIASshot/Config.cs index 6a79cc4..9dc0629 100644 --- a/TIASshot/Config.cs +++ b/TIASshot/Config.cs @@ -40,6 +40,7 @@ public static float GetFloat(string param) { float value = 0.0f; XmlNode node = doc.SelectSingleNode($"//Config/{param}"); + if (node == null) throw new ArgumentException($"Parameter '{param}' not found in config file."); if (node != null && float.TryParse(node.InnerText, out float result)) { value = result; @@ -55,6 +56,7 @@ public static int GetInt(string param) { int value = 0; XmlNode node = doc.SelectSingleNode($"//Config/{param}"); + if (node == null) throw new ArgumentException($"Parameter '{param}' not found in config file."); if (node != null && int.TryParse(node.InnerText, out int result)) { value = result; @@ -69,6 +71,8 @@ /// public static string GetString(string param) { XmlNode node = doc.SelectSingleNode($"//Config/{param}"); + if (node == null) throw new ArgumentException($"Parameter '{param}' not found in config file."); + return node?.InnerText ?? ""; } } diff --git a/TIASshot/Form1.cs b/TIASshot/Form1.cs index 3910e60..e14be8c 100644 --- a/TIASshot/Form1.cs +++ b/TIASshot/Form1.cs @@ -68,12 +68,18 @@ txtDeviceName.Text = _camera.DeviceName; txtSerialNo.Text = _camera.SerialNumber; + txtNumMultiShots.Text = Config.GetString("Shot/MultiShotCount"); + txtMultiShotsInterval.Text = Config.GetString("Shot/MultiShotInterval"); + txtDataName.Text = Config.GetString("Shot/SubjectName"); txtSaveFolder.Text = Config.GetString("SaveFolder"); EnableShots(false); //_lucam.StartStopPreview(); if (_light.Open()) { _light.SetLight(); + } else { + Debug.WriteLine("光源の接続に失敗しました"); + btnLightSW.Enabled = false; } } @@ -204,7 +210,7 @@ /// /// private void btnLightSW_Click(object sender, EventArgs e) { - if (!_light.IsOpen) return; + if (!_light.IsOpen || _camera.IsCalibrating) return; if (_isLightOn) { _light.TurnOff(); btnLightSW.Text = "照明 ON"; diff --git a/TIASshot/IScam.cs b/TIASshot/IScam.cs index bc68195..089a174 100644 --- a/TIASshot/IScam.cs +++ b/TIASshot/IScam.cs @@ -68,13 +68,6 @@ _whiteBalanceBlue = _ic.VCDPropertyItems.Find(VCDGUIDs.VCDID_WhiteBalance, VCDGUIDs.VCDElement_WhiteBalanceBlue); _whiteBalanceGreen = _ic.VCDPropertyItems.Find(VCDGUIDs.VCDID_WhiteBalance, VCDGUIDs.VCDElement_WhiteBalanceGreen); _whiteBalanceRed = _ic.VCDPropertyItems.Find(VCDGUIDs.VCDID_WhiteBalance, VCDGUIDs.VCDElement_WhiteBalanceRed); - //_brightness.Value = Config.GetInt("ISCAM/Brightness"); - //_gain.Value = Config.GetInt("ISCAM/Gain"); - //_exposure.Value = Config.GetInt("ISCAM/Exposure"); - //_gamma.Value = Config.GetInt("ISCAM/Gamma"); - //_whiteBalanceBlue.Value = Config.GetInt("ISCAM/WBBlue"); - //_whiteBalanceGreen.Value = Config.GetInt("ISCAM/WBGreen"); - //_whiteBalanceRed.Value = Config.GetInt("ISCAM/WBRed"); Debug.WriteLine($"Exposure range: {_exposure.RangeMin} to {_exposure.RangeMax} value={_exposure.Value}" ); Debug.WriteLine($"Gain range: {_gain.RangeMin} to {_gain.RangeMax} value={_gain.Value}"); Debug.WriteLine($"Brightness range: {_brightness.RangeMin} to {_brightness.RangeMax} value={_brightness.Value}"); @@ -109,10 +102,10 @@ if (_calibrating > 0) { var whitePatch = Cv2.Mean(imgt, _chartMasks[12]); Debug.WriteLine($"White patch R {whitePatch.Val2:.00} G {whitePatch.Val1:.00} B {whitePatch.Val0:.00}"); - if (_calibrating % Config.GetInt("CalibrationUpdateInterval") == 0) { - _whiteBalanceBlue.Value = (int)(0.5 + _whiteBalanceBlue.Value * GetRatio((float)whitePatch.Val0, Config.GetFloat("ReferenceB"))); - _whiteBalanceGreen.Value = (int)(0.5 + _whiteBalanceGreen.Value * GetRatio((float)whitePatch.Val1, Config.GetFloat("ReferenceG"))); - _whiteBalanceRed.Value = (int)(0.5 + _whiteBalanceRed.Value * GetRatio((float)whitePatch.Val2, Config.GetFloat("ReferenceR"))); + if (_calibrating % Config.GetInt("Calib/UpdateInterval") == 0) { + _whiteBalanceBlue.Value = (int)(0.5 + _whiteBalanceBlue.Value * GetRatio((float)whitePatch.Val0, Config.GetFloat("Calib/Reference/B"))); + _whiteBalanceGreen.Value = (int)(0.5 + _whiteBalanceGreen.Value * GetRatio((float)whitePatch.Val1, Config.GetFloat("Calib/Reference/G"))); + _whiteBalanceRed.Value = (int)(0.5 + _whiteBalanceRed.Value * GetRatio((float)whitePatch.Val2, Config.GetFloat("Calib/Reference/R"))); } _calibrating--; if (_calibrating == 0) { @@ -153,7 +146,7 @@ var thread = new Thread(() => SaveThread(numImages)); thread.Start(); - var filename = Config.GetString("ShotTimeFile"); + var filename = Config.GetString("File/ShotTime"); using (var csv = new StreamWriter(Path.Combine(_saveFolder, filename))) { csv.WriteLine("Shot,Time(ms)"); diff --git a/TIASshot/LightSource.cs b/TIASshot/LightSource.cs index 927c626..910c910 100644 --- a/TIASshot/LightSource.cs +++ b/TIASshot/LightSource.cs @@ -42,10 +42,10 @@ /// /// public bool Open() { - var portName = FindPort(Config.GetString("LightSourceDevice")); + var portName = FindPort(Config.GetString("LightSource/Device")); if (portName == "") return false; _serial.PortName = portName; - _serial.BaudRate = Config.GetInt("LSBaudRate"); + _serial.BaudRate = Config.GetInt("LightSource/BaudRate"); _serial.DataBits = 8; _serial.StopBits = StopBits.One; _serial.Parity = Parity.None; diff --git a/TIASshot/Lucam.cs b/TIASshot/Lucam.cs index 3ab1917..add9d28 100644 --- a/TIASshot/Lucam.cs +++ b/TIASshot/Lucam.cs @@ -170,11 +170,11 @@ if (_calibrating > 0) { var whitePatch = Cv2.Mean(imgt, _chartMasks[12]); Debug.WriteLine($"White patch R {whitePatch.Val2:.00} G {whitePatch.Val1:.00} B {whitePatch.Val0:.00}"); - if (_calibrating % Config.GetInt("CalibrationUpdateInterval") == 0) { - _snap.GainBlue *= GetRatio((float)whitePatch.Val0, Config.GetFloat("ReferenceB")); - _snap.GainGrn1 *= GetRatio((float)whitePatch.Val1, Config.GetFloat("ReferenceG")); + if (_calibrating % Config.GetInt("Calib/UpdateInterval") == 0) { + _snap.GainBlue *= GetRatio((float)whitePatch.Val0, Config.GetFloat("Calib/Reference/B")); + _snap.GainGrn1 *= GetRatio((float)whitePatch.Val1, Config.GetFloat("Calib/Reference/G")); _snap.GainGrn2 = _snap.GainGrn1; - _snap.GainRed *= GetRatio((float)whitePatch.Val2, Config.GetFloat("ReferenceR")); + _snap.GainRed *= GetRatio((float)whitePatch.Val2, Config.GetFloat("Calib/Reference/R")); SetCameraParam(); } _calibrating--; @@ -210,7 +210,7 @@ var rawImage = new byte[imageSize]; var rgbImage = new byte[imageSize * 3]; - var filename = Config.GetString("ShotTimeFile"); + var filename = Config.GetString("File/ShotTime"); using (var csv = new StreamWriter(Path.Combine(_saveFolder, filename))) { csv.WriteLine("Shot,Time(ms)"); diff --git a/TIASshot/Properties/AssemblyInfo.cs b/TIASshot/Properties/AssemblyInfo.cs index 9a4a387..53d0db9 100644 --- a/TIASshot/Properties/AssemblyInfo.cs +++ b/TIASshot/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ // ビルド番号 // リビジョン // -[assembly: AssemblyVersion("1.3.0.0")] -[assembly: AssemblyFileVersion("1.3.0.0")] +[assembly: AssemblyVersion("1.4.0.0")] +[assembly: AssemblyFileVersion("1.4.0.0")] diff --git a/TIASshot/config.xml b/TIASshot/config.xml index d4b43c5..d954adb 100644 --- a/TIASshot/config.xml +++ b/TIASshot/config.xml @@ -1,44 +1,47 @@ - + 60.0 1.5 2.62 1.72 1.70 - - 226 - 226 - 226 - 0.8 - 100 - 5 - 5 - 1000 - 4.0 - 30 - tcc_srgb.csv - tcc_xyz.csv - tcc.png - tcc_rois.jpg - Shot0000.bmp - Srgb0000.jpg - ShotTime.csv - tcc_rgb.csv - tcc_srgb.csv - conv_rgb_srgb.csv - conv_rgb_xyz.csv - conv_srgb_xyz.csv - C:\TIAS_Data - Silicon Labs CP210x - 38400 + + 4.0 + 30 + + 226 + 226 + 226 + + 0.8 + 100 + 5 + 4,10,17 + + + 5 + 100 + 無記名 + + + C:\TIAS_Data + tcc_srgb.csv + tcc_xyz.csv + tcc.png + tcc_rois.jpg + Shot{NO}.bmp + Srgb{CN}_{NO}.jpg + ShotTime.csv + tcc_rgb.csv + tcc_srgb{CN}.csv + conv_rgb_srgb{CN}.csv + conv_rgb_xyz{CN}.csv + conv_srgb_xyz{CN}.csv + + + Silicon Labs CP210x + 38400 +