diff --git a/TIASshot.sln b/TIASshot.sln index 343f189..f86d34a 100644 --- a/TIASshot.sln +++ b/TIASshot.sln @@ -12,14 +12,14 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1D3B69FE-D6B4-42B9-BBCC-B8CB9CF3DE4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1D3B69FE-D6B4-42B9-BBCC-B8CB9CF3DE4F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1D3B69FE-D6B4-42B9-BBCC-B8CB9CF3DE4F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1D3B69FE-D6B4-42B9-BBCC-B8CB9CF3DE4F}.Release|Any CPU.Build.0 = Release|Any CPU + {1D3B69FE-D6B4-42B9-BBCC-B8CB9CF3DE4F}.Debug|x64.ActiveCfg = Debug|x64 + {1D3B69FE-D6B4-42B9-BBCC-B8CB9CF3DE4F}.Debug|x64.Build.0 = Debug|x64 + {1D3B69FE-D6B4-42B9-BBCC-B8CB9CF3DE4F}.Release|x64.ActiveCfg = Release|x64 + {1D3B69FE-D6B4-42B9-BBCC-B8CB9CF3DE4F}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TIASshot/CameraBase.cs b/TIASshot/CameraBase.cs new file mode 100644 index 0000000..41b9356 --- /dev/null +++ b/TIASshot/CameraBase.cs @@ -0,0 +1,489 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Eventing.Reader; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Runtime.Remoting.Channels; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Controls; +using System.Windows.Forms; +using OpenCvSharp; +using OpenCvSharp.Aruco; + +namespace TIASshot { + internal abstract class CameraBase { + // インターフェース + public abstract bool Connect(); + public abstract void Disconnect(); + protected abstract void Shot(int numImages=1, int interval=0); + + // プロパティ + 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; + protected int _calibrating = 0; + protected bool _calibrated = false; + protected Bitmap[] _bmps = new Bitmap[2]; + 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(); + + // プライベートメンバ + 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), + }; + 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[] { 1, 2, 3 }, // GB + new int[] { 2, 0, 3 }, // BR + new int[] { 0, 0, 3 }, // RR + new int[] { 1, 1, 3 }, // GG + new int[] { 2, 2, 3 }, // BB + new int[] { 0, 0, 1 }, // RRG + new int[] { 0, 0, 2 }, // RRB + new int[] { 1, 1, 2 }, // GGB + new int[] { 1, 1, 0 }, // GGR + 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); + + /// + /// カメラの基本クラス + /// + public CameraBase(Form1 form) { + _form = form; + Config.Load(); + 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(); + } + + /// + /// 起動チェック + /// + /// + protected bool BootCheck() { + + if (!Config.IsLoaded()) { + ErrorMsg = "設定ファイル(Config.xml)の読み込みに失敗しました.\r\n終了します."; + return false; + } + if (TCC_SRGB is null) { + ErrorMsg = $"ファイル({Config.GetString("File/TccSrgbTable")})の読み込みに失敗しました.\r\n終了します."; + return false; + } + if (TCC_XYZ is null) { + ErrorMsg = $"ファイル({Config.GetString("File/TccXyzTable")})の読み込みに失敗しました.\r\n終了します."; + return false; + } + return true; + } + + /// + /// 画像撮影1枚 + /// + public void ShotOne() { + SetSaveFolder(_form.GetDataName()); + Shot(); + _form.ShowMessage("撮影終了"); + } + + /// + /// 複数画像撮影 + /// + public void ShotMulti() { + SetSaveFolder(_form.GetDataName()); + Shot(_form.GetNumMultiShots(), _form.GetMultiShotsInterval()); + _form.ShowMessage("撮影終了"); + } + + /// + /// 画像保存処理 + /// + /// + /// + protected void SaveImages(Mat img, int idx) { + + var filename = Config.GetString("File/ImageRgb"); + 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])) { + filename = GetFilenameWithChannel("File/ImageSrgb", channel); + filename = filename.Replace("{NO}", $"{idx + 1:0000}"); + Cv2.ImWrite(Path.Combine(_saveFolder, filename), converted); + } + } + } + + /// + /// 画像保存スレッド処理 + /// + /// + protected void SaveThread(int numImages) { + int saveCount = 0; + while (saveCount < numImages) { + while(_shots.Count <= saveCount) { + Thread.Sleep(1); + } + SaveImages(_shots[saveCount], saveCount); + saveCount++; + _form.ShowMessage($"画像{saveCount}/{numImages}枚目保存"); + } + _form.ShowMessage("全ての画像保存完了"); + _form.EnableShots(true); + } + + /// + /// チャートの検出 + /// + /// + protected void DetectChart(Mat img) { + + // ARマーカー検出 + CvAruco.DetectMarkers(img, ARDict, out var corners, out var ids, + new DetectorParameters(), out var rejectedImgPoints); + if (ids.Length < 1) return; + + // マーカー座標格納 + var ptsPict = new List(); + var ptsModel = new List(); + 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; + if (y40 > y41) { + _form.ShowMessage("舌診チャートが上下逆方向です"); + return; + } + + // チャートの固定判定 + _form.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; + + // ホモグラフィの計算 + 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()); + + // チャートマスク作成 + _chartMasks.Clear(); + 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); + } + } + + _form.ShowMessage("舌診チャート検出 校正中"); + _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; + } + + /// + /// チャートに基づく変換行列の計算 + /// + /// + protected void CalcTcc(Mat img) { + + // 変換行列の計算 + var tccRgb = GetTccRgb(img); + var imgRois = GetTccRoisImage(img); + + // データ保存 + SetSaveFolder("校正"); + 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(); + _calibrated = true; + } + + /// + /// 画像からチャートのRGB値算出 + /// + /// + /// + private Mat GetTccRgb(Mat img) { + var arrRGB = new Mat(24, 3, MatType.CV_64FC1); + for (int i = 0; i < _chartMasks.Count; i++) { + var rgb = Cv2.Mean(img, _chartMasks[i]); + arrRGB.At(i, 0) = rgb.Val0; // Blue + arrRGB.At(i, 1) = rgb.Val1; // Green + arrRGB.At(i, 2) = rgb.Val2; // Red + } + return arrRGB; + } + + /// + /// TCCのROI画像を取得 + /// + /// + /// + private Mat GetTccRoisImage(Mat img) { + var imgRois = img.Clone(); + for (int i = 0; i < _chartMasks.Count; i++) { + imgRois.SetTo(new Scalar(0, 200, 0), _chartMasks[i]); + } + return imgRois; + } + + /// + /// 変換行列算出 + /// + /// 画像 + /// 変換目標の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; + } + + /// + /// ゲイン値の更新比率計算 + /// + /// + /// + protected float GetRatio(float value, float target) { + float ratio = target / value; + ratio = (ratio - 1.0f) * (value == 255.0f ? 1.0f : UpdateRate) + 1.0f; + return ratio; + } + + /// + /// csvファイルからMatを読み込む + /// + /// + /// + /// + private Mat LoadMatFromCsv(string csvFile) { + try { + var arr = new List(); + int cols = -1; + using (var reader = new StreamReader(csvFile)) { + while (!reader.EndOfStream) { + var line = reader.ReadLine(); + var valStrs = line.Split(','); + if (cols == -1) cols = valStrs.Length; + else if (cols != valStrs.Length) throw new Exception("cols != valStrs.Length"); + var vals = valStrs.Select(x => double.Parse(x)).ToArray(); + arr.Add(vals); + } + } + var m = new Mat(arr.Count, cols, MatType.CV_64FC1); + for (var row = 0; row < arr.Count; row++) { + for (var col = 0; col < cols; col++) { + m.At(row, col) = arr[row][col]; + } + } + return m; + } catch (Exception) { + return null; + } + } + + /// + /// Matをcsvファイルに保存 + /// + /// + /// + private void SaveMatToCsv(string csvFile, Mat m) { + using (var writer = new StreamWriter(csvFile)) { + for (var row = 0; row < m.Rows; row++) { + var line = ""; + for (var col = 0; col < m.Cols; col++) { + if (col > 0) line += ","; + line += $"{m.At(row, col):0.0000000}"; + } + writer.WriteLine(line); + } + } + } + + /// + /// 保存フォルダの取得 + /// + /// + /// + protected void SetSaveFolder(string note) { + var dt = DateTime.Now; + var paths = new List() { + Config.GetString("SaveFolder"), + dt.ToString("yyyy-MM-dd"), + dt.ToString("HH_mm_ss") + $"-{note}", + }; + _saveFolder = Path.Combine(paths.ToArray()); + Directory.CreateDirectory(_saveFolder); + } + + } +} diff --git a/TIASshot/Config.cs b/TIASshot/Config.cs new file mode 100644 index 0000000..9dc0629 --- /dev/null +++ b/TIASshot/Config.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web.Configuration; +using System.Windows.Documents; +using System.Xml; + +namespace TIASshot { + internal static class Config { + static string configFile = "config.xml"; + static XmlDocument doc = new XmlDocument(); + + /// + /// 設定ファイルを読み込む + /// + public static void Load() { + try { + doc.Load(configFile); + } catch (Exception) { + } + } + + /// + /// 設定ファイル読み込み判定 + /// + /// + public static bool IsLoaded() { + XmlNode node = doc.SelectSingleNode($"//Config"); + return node != null; + } + + /// + /// 設定ファイルからfloat値を取得 + /// + /// + /// + 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; + } + return value; + } + + /// + /// 設定ファイルからint値を取得 + /// + /// + /// + 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; + } + return value; + } + + /// + /// 設定ファイルから文字列を取得 + /// + /// + /// + 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/DBK33UX178.xml b/TIASshot/DBK33UX178.xml new file mode 100644 index 0000000..38c40fc --- /dev/null +++ b/TIASshot/DBK33UX178.xml @@ -0,0 +1,144 @@ + + + RGB24 (3072x2048) + 30.000030 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TIASshot/DFK23UX249.xml b/TIASshot/DFK23UX249.xml new file mode 100644 index 0000000..024be0d --- /dev/null +++ b/TIASshot/DFK23UX249.xml @@ -0,0 +1,133 @@ + + + RGB24 (1920x1200) + 30.000030 + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TIASshot/Form1.Designer.cs b/TIASshot/Form1.Designer.cs index f9252c3..50db299 100644 --- a/TIASshot/Form1.Designer.cs +++ b/TIASshot/Form1.Designer.cs @@ -23,66 +23,88 @@ /// コード エディターで変更しないでください。 /// private void InitializeComponent() { - this.button1 = new System.Windows.Forms.Button(); + this.btnShotOne = new System.Windows.Forms.Button(); this.label1 = new System.Windows.Forms.Label(); this.label2 = new System.Windows.Forms.Label(); this.txtDeviceName = new System.Windows.Forms.TextBox(); this.txtSerialNo = new System.Windows.Forms.TextBox(); this.picPreview = new System.Windows.Forms.PictureBox(); this.picDisplay = new System.Windows.Forms.PictureBox(); + this.btnShotMulti = new System.Windows.Forms.Button(); + this.btnCalib = new System.Windows.Forms.Button(); + this.txtMessage = new System.Windows.Forms.TextBox(); + this.txtSaveFolder = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.txtDataName = new System.Windows.Forms.TextBox(); + this.label5 = new System.Windows.Forms.Label(); + this.label6 = new System.Windows.Forms.Label(); + this.txtNumMultiShots = new System.Windows.Forms.TextBox(); + this.label7 = new System.Windows.Forms.Label(); + this.txtMultiShotsInterval = new System.Windows.Forms.TextBox(); + this.label8 = new System.Windows.Forms.Label(); + this.btnOpenDataFolder = new System.Windows.Forms.Button(); + this.btnLightSW = new System.Windows.Forms.Button(); + this.icImagingControl1 = new TIS.Imaging.ICImagingControl(); ((System.ComponentModel.ISupportInitialize)(this.picPreview)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.picDisplay)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.icImagingControl1)).BeginInit(); this.SuspendLayout(); // - // button1 + // btnShotOne // - this.button1.Location = new System.Drawing.Point(12, 76); - this.button1.Name = "button1"; - this.button1.Size = new System.Drawing.Size(110, 37); - this.button1.TabIndex = 0; - this.button1.Text = "button1"; - this.button1.UseVisualStyleBackColor = true; - this.button1.Click += new System.EventHandler(this.button1_Click); + this.btnShotOne.Font = new System.Drawing.Font("MS UI Gothic", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); + this.btnShotOne.Location = new System.Drawing.Point(15, 176); + this.btnShotOne.Name = "btnShotOne"; + this.btnShotOne.Size = new System.Drawing.Size(268, 47); + this.btnShotOne.TabIndex = 0; + this.btnShotOne.Text = "1枚撮影"; + this.btnShotOne.UseVisualStyleBackColor = true; + this.btnShotOne.Click += new System.EventHandler(this.btnShot1_Click); // // label1 // this.label1.AutoSize = true; + this.label1.Font = new System.Drawing.Font("MS UI Gothic", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); this.label1.Location = new System.Drawing.Point(12, 9); this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(44, 12); + this.label1.Size = new System.Drawing.Size(40, 16); this.label1.TabIndex = 1; - this.label1.Text = "Camera"; + this.label1.Text = "カメラ"; // // label2 // this.label2.AutoSize = true; - this.label2.Location = new System.Drawing.Point(12, 37); + this.label2.Font = new System.Drawing.Font("MS UI Gothic", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); + this.label2.Location = new System.Drawing.Point(157, 9); this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(26, 12); + this.label2.Size = new System.Drawing.Size(35, 16); this.label2.TabIndex = 1; this.label2.Text = "S/N"; // // txtDeviceName // - this.txtDeviceName.Location = new System.Drawing.Point(62, 6); + this.txtDeviceName.Font = new System.Drawing.Font("MS UI Gothic", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); + this.txtDeviceName.Location = new System.Drawing.Point(51, 6); this.txtDeviceName.Name = "txtDeviceName"; this.txtDeviceName.ReadOnly = true; - this.txtDeviceName.Size = new System.Drawing.Size(61, 19); + this.txtDeviceName.Size = new System.Drawing.Size(106, 23); this.txtDeviceName.TabIndex = 2; // // txtSerialNo // - this.txtSerialNo.Location = new System.Drawing.Point(51, 34); + this.txtSerialNo.Font = new System.Drawing.Font("MS UI Gothic", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); + this.txtSerialNo.Location = new System.Drawing.Point(192, 6); this.txtSerialNo.Name = "txtSerialNo"; this.txtSerialNo.ReadOnly = true; - this.txtSerialNo.Size = new System.Drawing.Size(72, 19); + this.txtSerialNo.Size = new System.Drawing.Size(94, 23); this.txtSerialNo.TabIndex = 3; // // picPreview // - this.picPreview.Location = new System.Drawing.Point(14, 129); + this.picPreview.Location = new System.Drawing.Point(12, 565); this.picPreview.Name = "picPreview"; - this.picPreview.Size = new System.Drawing.Size(115, 85); + this.picPreview.Size = new System.Drawing.Size(79, 57); this.picPreview.TabIndex = 4; this.picPreview.TabStop = false; this.picPreview.Visible = false; @@ -92,31 +114,216 @@ this.picDisplay.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.picDisplay.Location = new System.Drawing.Point(144, 6); + this.picDisplay.Location = new System.Drawing.Point(292, 6); this.picDisplay.Name = "picDisplay"; - this.picDisplay.Size = new System.Drawing.Size(488, 353); + this.picDisplay.Size = new System.Drawing.Size(449, 610); this.picDisplay.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; this.picDisplay.TabIndex = 5; this.picDisplay.TabStop = false; // + // btnShotMulti + // + this.btnShotMulti.AllowDrop = true; + this.btnShotMulti.Font = new System.Drawing.Font("MS UI Gothic", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); + this.btnShotMulti.Location = new System.Drawing.Point(15, 316); + this.btnShotMulti.Name = "btnShotMulti"; + this.btnShotMulti.Size = new System.Drawing.Size(268, 49); + this.btnShotMulti.TabIndex = 6; + this.btnShotMulti.Text = "連続撮影"; + this.btnShotMulti.UseVisualStyleBackColor = true; + this.btnShotMulti.Click += new System.EventHandler(this.btnShotMulti_Click); + // + // btnCalib + // + this.btnCalib.AllowDrop = true; + this.btnCalib.Location = new System.Drawing.Point(100, 579); + this.btnCalib.Name = "btnCalib"; + this.btnCalib.Size = new System.Drawing.Size(88, 37); + this.btnCalib.TabIndex = 7; + this.btnCalib.Text = "校正"; + this.btnCalib.UseVisualStyleBackColor = true; + this.btnCalib.Visible = false; + this.btnCalib.Click += new System.EventHandler(this.btnCalib_Click); + // + // txtMessage + // + this.txtMessage.BackColor = System.Drawing.Color.LemonChiffon; + this.txtMessage.Font = new System.Drawing.Font("MS UI Gothic", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); + this.txtMessage.Location = new System.Drawing.Point(15, 45); + this.txtMessage.Name = "txtMessage"; + this.txtMessage.ReadOnly = true; + this.txtMessage.Size = new System.Drawing.Size(268, 26); + this.txtMessage.TabIndex = 9; + this.txtMessage.Text = "舌診チャートを設置してください."; + // + // txtSaveFolder + // + this.txtSaveFolder.Font = new System.Drawing.Font("MS UI Gothic", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); + this.txtSaveFolder.Location = new System.Drawing.Point(78, 463); + this.txtSaveFolder.Name = "txtSaveFolder"; + this.txtSaveFolder.ReadOnly = true; + this.txtSaveFolder.Size = new System.Drawing.Size(205, 23); + this.txtSaveFolder.TabIndex = 10; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Font = new System.Drawing.Font("MS UI Gothic", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); + this.label3.Location = new System.Drawing.Point(12, 440); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(123, 16); + this.label3.TabIndex = 11; + this.label3.Text = "データ保存フォルダ"; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Font = new System.Drawing.Font("MS UI Gothic", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); + this.label4.Location = new System.Drawing.Point(12, 95); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(60, 16); + this.label4.TabIndex = 12; + this.label4.Text = "データ名"; + // + // txtDataName + // + this.txtDataName.Font = new System.Drawing.Font("MS UI Gothic", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); + this.txtDataName.Location = new System.Drawing.Point(15, 114); + this.txtDataName.Name = "txtDataName"; + this.txtDataName.Size = new System.Drawing.Size(268, 23); + this.txtDataName.TabIndex = 13; + this.txtDataName.Text = "なし"; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Font = new System.Drawing.Font("MS UI Gothic", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); + this.label5.Location = new System.Drawing.Point(12, 261); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(103, 16); + this.label5.TabIndex = 14; + this.label5.Text = "連続撮影枚数"; + // + // label6 + // + this.label6.AutoSize = true; + this.label6.Font = new System.Drawing.Font("MS UI Gothic", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); + this.label6.Location = new System.Drawing.Point(12, 290); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(71, 16); + this.label6.TabIndex = 15; + this.label6.Text = "撮影間隔"; + // + // txtNumMultiShots + // + this.txtNumMultiShots.Font = new System.Drawing.Font("MS UI Gothic", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); + this.txtNumMultiShots.Location = new System.Drawing.Point(121, 258); + this.txtNumMultiShots.Name = "txtNumMultiShots"; + this.txtNumMultiShots.Size = new System.Drawing.Size(67, 23); + this.txtNumMultiShots.TabIndex = 16; + this.txtNumMultiShots.Text = "5"; + this.txtNumMultiShots.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + // + // label7 + // + this.label7.AutoSize = true; + this.label7.Font = new System.Drawing.Font("MS UI Gothic", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); + this.label7.Location = new System.Drawing.Point(194, 261); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(23, 16); + this.label7.TabIndex = 17; + this.label7.Text = "枚"; + // + // txtMultiShotsInterval + // + this.txtMultiShotsInterval.Font = new System.Drawing.Font("MS UI Gothic", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); + this.txtMultiShotsInterval.Location = new System.Drawing.Point(121, 287); + this.txtMultiShotsInterval.Name = "txtMultiShotsInterval"; + this.txtMultiShotsInterval.Size = new System.Drawing.Size(67, 23); + this.txtMultiShotsInterval.TabIndex = 18; + this.txtMultiShotsInterval.Text = "1000"; + this.txtMultiShotsInterval.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + // + // label8 + // + this.label8.AutoSize = true; + this.label8.Font = new System.Drawing.Font("MS UI Gothic", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); + this.label8.Location = new System.Drawing.Point(194, 290); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(42, 16); + this.label8.TabIndex = 19; + this.label8.Text = "ミリ秒"; + // + // btnOpenDataFolder + // + this.btnOpenDataFolder.Font = new System.Drawing.Font("MS UI Gothic", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); + this.btnOpenDataFolder.Location = new System.Drawing.Point(15, 459); + this.btnOpenDataFolder.Name = "btnOpenDataFolder"; + this.btnOpenDataFolder.Size = new System.Drawing.Size(57, 27); + this.btnOpenDataFolder.TabIndex = 20; + this.btnOpenDataFolder.Text = "開く"; + this.btnOpenDataFolder.UseVisualStyleBackColor = true; + this.btnOpenDataFolder.Click += new System.EventHandler(this.btnOpenDataFolder_Click); + // + // btnLightSW + // + this.btnLightSW.AllowDrop = true; + this.btnLightSW.Location = new System.Drawing.Point(173, 517); + this.btnLightSW.Name = "btnLightSW"; + this.btnLightSW.Size = new System.Drawing.Size(110, 37); + this.btnLightSW.TabIndex = 21; + this.btnLightSW.Text = "照明 OFF"; + this.btnLightSW.UseVisualStyleBackColor = true; + this.btnLightSW.Click += new System.EventHandler(this.btnLightSW_Click); + // + // icImagingControl1 + // + this.icImagingControl1.BackColor = System.Drawing.Color.White; + this.icImagingControl1.DeviceListChangedExecutionMode = TIS.Imaging.EventExecutionMode.Invoke; + this.icImagingControl1.DeviceLostExecutionMode = TIS.Imaging.EventExecutionMode.AsyncInvoke; + this.icImagingControl1.ImageAvailableExecutionMode = TIS.Imaging.EventExecutionMode.MultiThreaded; + this.icImagingControl1.LiveDisplayPosition = new System.Drawing.Point(0, 0); + this.icImagingControl1.Location = new System.Drawing.Point(12, 505); + this.icImagingControl1.Name = "icImagingControl1"; + this.icImagingControl1.Size = new System.Drawing.Size(79, 54); + this.icImagingControl1.TabIndex = 22; + this.icImagingControl1.Visible = false; + // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(644, 377); + this.ClientSize = new System.Drawing.Size(753, 634); + this.Controls.Add(this.icImagingControl1); + this.Controls.Add(this.btnLightSW); + this.Controls.Add(this.btnOpenDataFolder); + this.Controls.Add(this.label8); + this.Controls.Add(this.txtMultiShotsInterval); + this.Controls.Add(this.label7); + this.Controls.Add(this.txtNumMultiShots); + this.Controls.Add(this.label6); + this.Controls.Add(this.label5); + this.Controls.Add(this.txtDataName); + this.Controls.Add(this.label4); + this.Controls.Add(this.label3); + this.Controls.Add(this.txtSaveFolder); + this.Controls.Add(this.txtMessage); + this.Controls.Add(this.btnCalib); + this.Controls.Add(this.btnShotMulti); this.Controls.Add(this.picDisplay); this.Controls.Add(this.picPreview); this.Controls.Add(this.txtSerialNo); this.Controls.Add(this.txtDeviceName); this.Controls.Add(this.label2); this.Controls.Add(this.label1); - this.Controls.Add(this.button1); + this.Controls.Add(this.btnShotOne); this.Name = "Form1"; this.Text = "Form1"; this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Form1_FormClosing); this.Load += new System.EventHandler(this.Form1_Load); ((System.ComponentModel.ISupportInitialize)(this.picPreview)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.picDisplay)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.icImagingControl1)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); @@ -124,13 +331,29 @@ #endregion - private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button btnShotOne; private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label2; private System.Windows.Forms.TextBox txtDeviceName; private System.Windows.Forms.TextBox txtSerialNo; private System.Windows.Forms.PictureBox picPreview; private System.Windows.Forms.PictureBox picDisplay; + private System.Windows.Forms.Button btnShotMulti; + private System.Windows.Forms.Button btnCalib; + private System.Windows.Forms.TextBox txtMessage; + private System.Windows.Forms.TextBox txtSaveFolder; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.TextBox txtDataName; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.TextBox txtNumMultiShots; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.TextBox txtMultiShotsInterval; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.Button btnOpenDataFolder; + private System.Windows.Forms.Button btnLightSW; + private TIS.Imaging.ICImagingControl icImagingControl1; } } diff --git a/TIASshot/Form1.cs b/TIASshot/Form1.cs index da7b418..e14be8c 100644 --- a/TIASshot/Form1.cs +++ b/TIASshot/Form1.cs @@ -2,18 +2,25 @@ using System.Collections.Generic; using System.ComponentModel; using System.Data; +using System.Diagnostics; using System.Drawing; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; +using System.Windows.Interop; +using System.Windows.Media.Media3D; +using System.Xml.Serialization; +using TIS.Imaging; namespace TIASshot { public partial class Form1 : Form { - const string APP_NAME = "TIAS Shot"; - Lucam _lucam; + private CameraBase _camera; + public static string APP_NAME = "TIAS Shot"; + private LightSource _light = new LightSource(); + private bool _isLightOn = true; /// /// コンストラクタ @@ -21,8 +28,6 @@ public Form1() { InitializeComponent(); - _lucam = new Lucam(picPreview, picDisplay); - var version = Assembly.GetExecutingAssembly().GetName().Version; Text = $"{APP_NAME} ver.{version.Major}.{version.Minor}"; #if DEBUG @@ -30,24 +35,52 @@ #endif } - private void button1_Click(object sender, EventArgs e) { - - } - /// /// フォームロード /// /// /// private void Form1_Load(object sender, EventArgs e) { - if (!_lucam.Connect()) { - MessageBox.Show("カメラが見つかりません.\r\n終了します.", APP_NAME, + _camera = new Lucam(this, picPreview); + if (!_camera.Connect()) { + Debug.WriteLine(_camera.ErrorMsg); + Debug.WriteLine("Lucamの接続に失敗しました"); + _camera = null; + } + + if (_camera == null) { + var cameraList = icImagingControl1.Devices.Select(d => d.Name.Replace(" ", "")); + foreach (var cam in cameraList) { + _camera = new IScam(this, icImagingControl1, cam); + if (_camera.Connect()) break; + Debug.WriteLine(_camera.ErrorMsg); + Debug.WriteLine($"{cam}の接続に失敗しました"); + _camera = null; + } + } + + if (_camera == null) { + MessageBox.Show("カメラが見つかりません.終了します.", APP_NAME, MessageBoxButtons.OK, MessageBoxIcon.Warning); Close(); + return; } - txtDeviceName.Text = _lucam.DeviceName; - txtSerialNo.Text = _lucam.SerialNumber; - _lucam.StartStopPreview(); + + 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; + } } /// @@ -56,7 +89,137 @@ /// /// private void Form1_FormClosing(object sender, FormClosingEventArgs e) { - _lucam.Disconnect(); + _camera.Disconnect(); + } + + /// + /// 画像撮影1枚 + /// + /// + /// + private void btnShot1_Click(object sender, EventArgs e) { + EnableShots(false); + _camera.ShotOne(); + } + + /// + /// 複数画像撮影 + /// + /// + /// + private void btnShotMulti_Click(object sender, EventArgs e) { + EnableShots(false); + _camera.ShotMulti(); + } + + /// + /// 校正 + /// + /// + /// + private void btnCalib_Click(object sender, EventArgs e) { + //_lucam.Calibration(); + } + + /// + /// 画像表示 + /// + /// + public void ShowImage(Bitmap bmp) { + // なぜかわからないがここはInvokeなしでworkerスレッドから呼び出しできる + //if (InvokeRequired) { + // Invoke((MethodInvoker)delegate { ShowImage(bmp); }); + // return; + //} + picDisplay.Image = bmp; + } + + /// + /// メッセージ表示 + /// + /// + public void ShowMessage(string msg) { + if (InvokeRequired) { + Invoke((MethodInvoker)delegate { ShowMessage(msg); }); + return; + } + txtMessage.Text = msg; + txtMessage.Update(); + } + + /// + /// データ名取得 + /// + /// + public string GetDataName() { + if (InvokeRequired) { + return (string)Invoke((MethodInvoker)delegate { GetDataName(); }); + } + return txtDataName.Text; + } + + /// + /// マルチショット撮影の枚数取得 + /// + /// + public int GetNumMultiShots() { + if (InvokeRequired) { + return (int)Invoke((MethodInvoker)delegate { GetNumMultiShots(); }); + } + if (int.TryParse(txtNumMultiShots.Text, out int result)) return result; + return 1; + } + + /// + /// マルチショット撮影の間隔取得 + /// + /// + public int GetMultiShotsInterval() { + if (InvokeRequired) { + return (int)Invoke((MethodInvoker)delegate { GetMultiShotsInterval(); }); + } + if (int.TryParse(txtMultiShotsInterval.Text, out int result)) return result; + return 1000; + } + + /// + /// 撮影ボタン有効化 + /// + /// + public void EnableShots(bool enable = true) { + if (InvokeRequired) { + Invoke((MethodInvoker)delegate { EnableShots(enable); }); + return; + } + btnShotOne.Enabled = enable; + btnShotMulti.Enabled = enable; + } + + /// + /// データフォルダを開く + /// + /// + /// + private void btnOpenDataFolder_Click(object sender, EventArgs e) { + System.Diagnostics.Process.Start(txtSaveFolder.Text); + } + + /// + /// 光源のON/OFF切り替え + /// + /// + /// + private void btnLightSW_Click(object sender, EventArgs e) { + if (!_light.IsOpen || _camera.IsCalibrating) return; + if (_isLightOn) { + _light.TurnOff(); + btnLightSW.Text = "照明 ON"; + _isLightOn = false; + } else { + _light.SetLight(); + btnLightSW.Text = "照明 OFF"; + _isLightOn = true; + } } } } diff --git a/TIASshot/IScam.cs b/TIASshot/IScam.cs new file mode 100644 index 0000000..089a174 --- /dev/null +++ b/TIASshot/IScam.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using TIS.Imaging; +using OpenCvSharp; +using OpenCvSharp.Extensions; +using System.Diagnostics; +using System.Drawing; +using System.Threading; +using System.IO; + +namespace TIASshot { + internal class IScam : CameraBase { + ICImagingControl _ic; + VCDRangeProperty _brightness; + VCDRangeProperty _gain; + VCDRangeProperty _exposure; + VCDRangeProperty _gamma; + VCDRangeProperty _whiteBalanceBlue; + VCDRangeProperty _whiteBalanceGreen; + VCDRangeProperty _whiteBalanceRed; + FrameQueueSink _queueSink; + FrameSnapSink _snapSink; + + /// + /// IScamコンストラクタ + /// + /// + /// + public IScam(Form1 form, ICImagingControl ic, string deviceName) : base(form) { + _ic = ic; + DeviceName = deviceName; + } + + /// + /// IScam接続 + /// + /// + public override bool Connect() { + if (!BootCheck()) return false; + + var configFile = $"{DeviceName}.xml"; + if (!File.Exists(configFile)) { + ErrorMsg = $"{configFile}が見つかりません"; + return false; + } + _ic.LoadDeviceStateFromFile(configFile, true); + if (!_ic.DeviceValid) { + ErrorMsg = $"設定ファイル{configFile}の読み込みに失敗しました"; + return false; + } + //if (!_ic.LoadShowSaveDeviceState(configFile)) { + // return false; + //} + + _queueSink = new FrameQueueSink(Retrieve, MediaSubtypes.RGB24, 5); + _snapSink = new FrameSnapSink(MediaSubtypes.RGB24); + _ic.Sink = _queueSink; + DeviceName = _ic.DeviceCurrent.Name; + SerialNumber = _ic.DeviceCurrent.GetSerialNumber(); + _brightness = _ic.VCDPropertyItems.Find(VCDGUIDs.VCDID_Brightness, VCDGUIDs.VCDElement_Value); + _gain = _ic.VCDPropertyItems.Find(VCDGUIDs.VCDID_Gain, VCDGUIDs.VCDElement_Value); + _exposure = _ic.VCDPropertyItems.Find(VCDGUIDs.VCDID_Exposure, VCDGUIDs.VCDElement_Value); + _gamma = _ic.VCDPropertyItems.Find(VCDGUIDs.VCDID_Gamma, VCDGUIDs.VCDElement_Value); + _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); + 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}"); + Debug.WriteLine($"Gamma range: {_gamma.RangeMin} to {_gamma.RangeMax} value={_gamma.Value}"); + Debug.WriteLine($"White Balance Blue range: {_whiteBalanceBlue.RangeMin} to {_whiteBalanceBlue.RangeMax} value={_whiteBalanceBlue.Value}"); + Debug.WriteLine($"White Balance Green range: {_whiteBalanceGreen.RangeMin} to {_whiteBalanceGreen.RangeMax} value={_whiteBalanceGreen.Value}"); + Debug.WriteLine($"White Balance Red range: {_whiteBalanceRed.RangeMin} to {_whiteBalanceRed.RangeMax} value={_whiteBalanceRed.Value}"); + + _ic.LiveStart(); + return true; + } + + /// + /// IScam切断 + /// + public override void Disconnect() { + _ic.LiveStop(); + } + + /// + /// フレーム取得処理 + /// + /// + /// + private FrameQueuedResult Retrieve(IFrameQueueBuffer buffer) { + var frameType = buffer.FrameType; + + using (Mat img = Mat.FromPixelData(frameType.Height, frameType.Width, MatType.CV_8UC3, buffer.GetIntPtr())) { + using (Mat imgt = img.T()) { + _bmps[_bmpIndex] = imgt.ToBitmap(); + + 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("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) { + Debug.WriteLine($"White Balance Blue range: {_whiteBalanceBlue.RangeMin} to {_whiteBalanceBlue.RangeMax} value={_whiteBalanceBlue.Value}"); + Debug.WriteLine($"White Balance Green range: {_whiteBalanceGreen.RangeMin} to {_whiteBalanceGreen.RangeMax} value={_whiteBalanceGreen.Value}"); + Debug.WriteLine($"White Balance Red range: {_whiteBalanceRed.RangeMin} to {_whiteBalanceRed.RangeMax} value={_whiteBalanceRed.Value}"); + + CalcTcc(imgt); + } + } + + if (!_calibrated && _calibrating == 0) { + DetectChart(imgt); + } + } + } + _form.ShowImage(_bmps[_bmpIndex]); + _bmpIndex = (_bmpIndex + 1) % 2; + if (_bmps[_bmpIndex] != null) _bmps[_bmpIndex].Dispose(); + + return FrameQueuedResult.ReQueue; + } + + /// + /// 撮影 + /// + /// + /// + protected override void Shot(int numImages = 1, int interval = 0) { + _ic.LiveStop(); + _ic.Sink = _snapSink; + _ic.LiveStart(); + var snapSink = _ic.Sink as FrameSnapSink; + + _shots.Clear(); + snapSink.SnapSingle(TimeSpan.FromSeconds(5)); // 最初のフレームを捨てる + + var thread = new Thread(() => SaveThread(numImages)); + thread.Start(); + + var filename = Config.GetString("File/ShotTime"); + using (var csv = new StreamWriter(Path.Combine(_saveFolder, filename))) { + csv.WriteLine("Shot,Time(ms)"); + + var watch = Stopwatch.StartNew(); + for (int i = 0; i < numImages; i++) { + var shotTime = watch.ElapsedMilliseconds; + csv.WriteLine($"{i+1},{shotTime}"); + _form.ShowMessage($"撮影 {i + 1} / {numImages} 枚目"); + var buffer = snapSink.SnapSingle(TimeSpan.FromSeconds(5)); + + using (Mat img = Mat.FromPixelData(buffer.FrameType.Height, buffer.FrameType.Width, MatType.CV_8UC3, buffer.GetIntPtr())) { + _shots.Add(img.T()); + } + while (watch.ElapsedMilliseconds < interval * (i+1) && i < numImages - 1) { + Thread.Sleep(1); + } + } + } + + _ic.LiveStop(); + _ic.Sink = _queueSink; + _ic.LiveStart(); + } + } +} diff --git a/TIASshot/LightSource.cs b/TIASshot/LightSource.cs new file mode 100644 index 0000000..910c910 --- /dev/null +++ b/TIASshot/LightSource.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO.Ports; +using System.Management; +using System.Diagnostics; + +namespace TIASshot { + internal class LightSource { + SerialPort _serial; + public bool IsOpen { get { return _serial.IsOpen; } } + + /// + /// コンストラクタ + /// + public LightSource() { + _serial = new SerialPort(); + } + + /// + /// ポートを探す + /// + /// + /// + private string FindPort(string deviceName) { + var portname = ""; + var mcW32SerPort = new ManagementClass("Win32_SerialPort"); + foreach (var port in mcW32SerPort.GetInstances()) { + var devName = port.GetPropertyValue("Caption").ToString(); + Debug.WriteLine(devName); + if (devName.Contains(deviceName)) { + portname = port.GetPropertyValue("DeviceID").ToString(); + } + } + return portname; + } + + /// + /// 接続 + /// + /// + public bool Open() { + var portName = FindPort(Config.GetString("LightSource/Device")); + if (portName == "") return false; + _serial.PortName = portName; + _serial.BaudRate = Config.GetInt("LightSource/BaudRate"); + _serial.DataBits = 8; + _serial.StopBits = StopBits.One; + _serial.Parity = Parity.None; + _serial.Open(); + return true; + } + + public void TurnOff() { + byte[] buf = new byte[] { 0x02, 0x44, 0x41, 0x58, 0x32, 0x3A, + 0x44, 0x41, 0x20, 0x30, 0x03, 0x31, 0x45, 0x04 }; + var str = Encoding.ASCII.GetString(buf); + _serial.Write(str); + } + + public void SetLight() { + byte[] buf = new byte[] { 0x02, 0x44, 0x41, 0x58, 0x32, 0x3A, 0x44, + 0x45, 0x46, 0x20, 0x33, 0x30, 0x37, 0x32, 0x03, 0x30, 0x34, 0x04 }; + var str = Encoding.ASCII.GetString(buf); + _serial.Write(str); + } + } +} diff --git a/TIASshot/Lucam.cs b/TIASshot/Lucam.cs index f5aafac..add9d28 100644 --- a/TIASshot/Lucam.cs +++ b/TIASshot/Lucam.cs @@ -10,64 +10,127 @@ using OpenCvSharp.Extensions; using System.Drawing; using System.Windows.Interop; +using System.Threading; +using OpenCvSharp.Aruco; +using System.Windows.Media.Animation; +using System.Configuration; +using System.Web.ModelBinding; +using System.Windows.Controls; +using System.IO; +using static System.Resources.ResXFileRef; +using System.Windows.Shapes; +using Path = System.IO.Path; +using OpenCvSharp.Dnn; namespace TIASshot { /// /// Lumeneraカメラクラス /// - internal class Lucam { + internal class Lucam : CameraBase { - public string DeviceName { get; private set; } = "Unknown"; - public string SerialNumber { get; private set; } + // 光源の輝度設定 + // 連続撮影のマルチスレッド化 + // GUI更新はタイマー使用に変更 - private IntPtr _hCam = IntPtr.Zero; - private PictureBox _picPreview, _picDisplay; - private bool _isPreview = false; - private dll.LucamRgbPreviewCallback _callbackHandler; - private int _callbackId; - private int _count = 0; - private Bitmap[] _bmps = new Bitmap[2]; - private int _bmpIndex = 0; + IntPtr _hCam = IntPtr.Zero; + PictureBox _picPreview; + + dll.LucamSnapshot _snap; + dll.LucamConversion _convert; + dll.LucamRgbPreviewCallback _callbackHandler; + int _callbackId; /// /// コンストラクタ /// /// /// - public Lucam(PictureBox preview, PictureBox display) { + public Lucam(Form1 form, PictureBox preview) : base(form) { _picPreview = preview; - _picDisplay = display; + + // カメラパラメータの初期値 + _snap.BufferLastFrame = false; + _snap.Exposure = Config.GetFloat("Lucam/Exposure"); + _snap.ExposureDelay = 0; + _snap.flReserved1 = 0; + _snap.flReserved2 = 0; + _snap.Format.BinningX = 1; + _snap.Format.BinningY = 1; + _snap.Format.FlagsX = 0; + _snap.Format.FlagsY = 0; + _snap.Format.Height = 1024; + _snap.Format.PixelFormat = dll.LucamPixelFormat.PF_8; + _snap.Format.SubSampleX = 1; + _snap.Format.SubSampleY = 1; + _snap.Format.Width = 1280; + _snap.Format.X = 0; + _snap.Format.Y = 0; + _snap.Gain = Config.GetFloat("Lucam/Gain"); + _snap.GainBlue = Config.GetFloat("Lucam/GainB"); + _snap.GainGrn1 = Config.GetFloat("Lucam/GainG"); + _snap.GainGrn2 = _snap.GainGrn1; + _snap.GainRed = Config.GetFloat("Lucam/GainR"); + _snap.ShutterType = dll.LucamShutterType.GlobalShutter; + _snap.StrobeDelay = 0.1f; + _snap.StrobeFlags = 0; + _snap.Timeout = 5000; + _snap.ulReserved2 = 0; + _snap.UseHwTrigger = false; + + _convert.DemosaicMethod = dll.LucamDemosaicMethod.HIGH_QUALITY; + _convert.CorrectionMatrix = dll.LucamCorrectionMatrix.LED; } /// /// カメラ接続 /// /// - public bool Connect() { - var numCam = dll.LucamNumCameras(); + public override bool Connect() { + if (!BootCheck()) return false; + + int numCam = 0; + try { + numCam = dll.LucamNumCameras(); + } catch (Exception ex) { + ErrorMsg = ex.Message; + return false; + } if ( numCam < 1 ) { + ErrorMsg = "Lucamが見つかりません"; + return false; + } + if (numCam > 1) { + ErrorMsg = "複数のLucamが見つかりました.\r\n正しいカメラを1つ接続してください."; return false; } + // カメラのデバイス情報取得 var lumVersion = new dll.LucamVersion[numCam]; lumVersion = api.EnumCameras(); var id = 0; if (lumVersion[id].CameraId == 0x49f) DeviceName = "Lw110"; SerialNumber = lumVersion[id].SerialNumber.ToString(); + // カメラを開く _hCam = api.CameraOpen(1); if (_hCam == IntPtr.Zero) { + ErrorMsg = "カメラの接続に失敗しました.\r\n他のアプリケーションでカメラを使用していないか確認してください."; return false; } + // プレビューコールバックの登録 _callbackHandler = new dll.LucamRgbPreviewCallback(PreviewCallback); _callbackId = dll.LucamAddRgbPreviewCallback(_hCam, _callbackHandler, IntPtr.Zero, dll.LucamPixelFormat.PF_24); if (_callbackId == -1) { - MessageBox.Show("Cannot add rgb preview callback, fail to start preview"); + ErrorMsg = "コールバックの登録に失敗しました"; return false; } + SetCameraParam(); // カメラパラメータの設定 + + StartStopPreview(); // プレビュー開始 + return true; } @@ -77,34 +140,22 @@ /// public bool StartStopPreview() { if (_isPreview) { + // プレビュー停止 var ret = dll.LucamStreamVideoControl(_hCam, dll.LucamStreamMode.STOP_STREAMING, _picPreview.Handle.ToInt32()); - if (!ret) { - return false; - } + Debug.WriteLine("プレビュー停止"); + if (!ret) return false; _isPreview = false; return true; } else { + // プレビュー開始 var ret = dll.LucamStreamVideoControl(_hCam, dll.LucamStreamMode.START_DISPLAY, _picPreview.Handle.ToInt32()); - if (!ret) { - return false; - } + if (!ret) return false; _isPreview = true; return true; } } /// - /// 画像表示(スレッドセーフ) - /// - void SafeDisplay() { - if (_picDisplay.InvokeRequired) { - _picDisplay.Invoke((MethodInvoker)delegate { SafeDisplay(); }); - return; - } - _picDisplay.Image = _bmps[_bmpIndex]; - } - - /// /// プレビューコールバック /// /// @@ -112,28 +163,132 @@ /// データサイズ /// void PreviewCallback(IntPtr pContext, IntPtr pData, int n, uint unused) { - //Debug.WriteLine(n); - using (Mat img = Mat.FromPixelData(1024, 1280, MatType.CV_8UC3, pData)) { + using (Mat img = Mat.FromPixelData(_snap.Format.Height, _snap.Format.Width, MatType.CV_8UC3, pData)) { using (Mat imgt = img.T()) { _bmps[_bmpIndex] = imgt.ToBitmap(); + + 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("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("Calib/Reference/R")); + SetCameraParam(); + } + _calibrating--; + if (_calibrating == 0) { + CalcTcc(imgt); + } + } + + if (!_calibrated && _calibrating == 0) { + DetectChart(imgt); + } } } - SafeDisplay(); - //_picDisplay.Image = _bmps[_bmpIndex]; + _form.ShowImage(_bmps[_bmpIndex]); _bmpIndex = (_bmpIndex + 1) % 2; if (_bmps[_bmpIndex] != null) _bmps[_bmpIndex].Dispose(); } /// + /// 撮影 + /// + /// + /// + protected override void Shot(int numImages=1, int interval=0) { + SetSnapParam(); + + _shots.Clear(); + var thread = new Thread(() => SaveThread(numImages)); + thread.Start(); + + dll.LucamEnableFastFrames(_hCam, ref _snap); + var imageSize = _snap.Format.Width * _snap.Format.Height; + var rawImage = new byte[imageSize]; + var rgbImage = new byte[imageSize * 3]; + + var filename = Config.GetString("File/ShotTime"); + using (var csv = new StreamWriter(Path.Combine(_saveFolder, filename))) { + csv.WriteLine("Shot,Time(ms)"); + + var watch = Stopwatch.StartNew(); + for (var i = 0; i < numImages; i++) { + var shotTime = watch.ElapsedMilliseconds; + csv.WriteLine($"{i + 1},{shotTime}"); + _form.ShowMessage($"撮影 {i + 1} / {numImages} 枚目"); + var ret = dll.LucamTakeFastFrame(_hCam, rawImage); + //Debug.WriteLine(ret); + + dll.LucamConvertFrameToRgb24(_hCam, rgbImage, rawImage, + _snap.Format.Width, _snap.Format.Height, dll.LucamPixelFormat.PF_8, ref _convert); + using (Mat img = Mat.FromPixelData(_snap.Format.Height, _snap.Format.Width, MatType.CV_8UC3, rgbImage)) { + _shots.Add(img.T()); + } + while (watch.ElapsedMilliseconds < interval * (i + 1) && i < numImages - 1) { + Thread.Sleep(1); + } + } + } + + rgbImage = null; + rawImage = null; + + dll.LucamDisableFastFrames(_hCam); + } + + /// + /// 校正 露光時間とホワイトバランスの自動調整 + /// + public void Calibration() { + Debug.WriteLine("校正前"); + SetSnapParam(); + _calibrating = 30; + } + + /// + /// 撮影パラメータの設定 + /// + private void SetSnapParam() { + dll.LucamPropertyFlag flag; + dll.LucamGetProperty(_hCam, dll.LucamProperty.EXPOSURE, out _snap.Exposure, out flag); + dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN, out _snap.Gain, out flag); + dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN_BLUE, out _snap.GainBlue, out flag); + dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN_GREEN1, out _snap.GainGrn1, out flag); + dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN_GREEN2, out _snap.GainGrn2, out flag); + dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN_RED, out _snap.GainRed, out flag); + Debug.WriteLine($"SetSnapParam Exp {_snap.Exposure} Gain {_snap.Gain} GainBlue {_snap.GainBlue} GainGrn1 {_snap.GainGrn1} GainGrn2 {_snap.GainGrn2} GainRed {_snap.GainRed}"); + } + + /// + /// カメラパラメータの設定 + /// + private void SetCameraParam() { + var flag = dll.LucamPropertyFlag.NONE; + dll.LucamSetProperty(_hCam, dll.LucamProperty.EXPOSURE, _snap.Exposure, flag); + dll.LucamSetProperty(_hCam, dll.LucamProperty.GAIN, _snap.Gain, flag); + dll.LucamSetProperty(_hCam, dll.LucamProperty.GAIN_BLUE, _snap.GainBlue, flag); + dll.LucamSetProperty(_hCam, dll.LucamProperty.GAIN_GREEN1, _snap.GainGrn1, flag); + dll.LucamSetProperty(_hCam, dll.LucamProperty.GAIN_GREEN2, _snap.GainGrn2, flag); + dll.LucamSetProperty(_hCam, dll.LucamProperty.GAIN_RED, _snap.GainRed, flag); + Debug.WriteLine($"SetCameraParam Exp {_snap.Exposure} Gain {_snap.Gain} GainBlue {_snap.GainBlue} GainGrn1 {_snap.GainGrn1} GainGrn2 {_snap.GainGrn2} GainRed {_snap.GainRed}"); + } + + + /// /// カメラ切断 /// - public void Disconnect() { - if (_isPreview) { - StartStopPreview(); + public override void Disconnect() { + if (_hCam != IntPtr.Zero) { + if (_isPreview) { + StartStopPreview(); + } + api.CameraClose(_hCam); + _hCam = IntPtr.Zero; + Debug.WriteLine("カメラ切断"); } - Debug.WriteLine("カメラ切断"); - api.CameraClose(_hCam); - _hCam = IntPtr.Zero; } } } diff --git a/TIASshot/Properties/AssemblyInfo.cs b/TIASshot/Properties/AssemblyInfo.cs index 1e015bb..53d0db9 100644 --- a/TIASshot/Properties/AssemblyInfo.cs +++ b/TIASshot/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ // ビルド番号 // リビジョン // -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("1.4.0.0")] +[assembly: AssemblyFileVersion("1.4.0.0")] diff --git a/TIASshot/TIASshot.csproj b/TIASshot/TIASshot.csproj index 29971ff..615c26e 100644 --- a/TIASshot/TIASshot.csproj +++ b/TIASshot/TIASshot.csproj @@ -25,6 +25,7 @@ DEBUG;TRACE prompt 4 + true AnyCPU @@ -34,6 +35,29 @@ TRACE prompt 4 + true + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + 7.3 + prompt + true + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + 7.3 + prompt + true + true @@ -58,6 +82,7 @@ ..\packages\System.Drawing.Common.8.0.0\lib\net462\System.Drawing.Common.dll + ..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll @@ -83,15 +108,23 @@ + + False + C:\Program Files (x86)\The Imaging Source Europe GmbH\IC Imaging Control 3.5 .NET Library\bin\x64\TIS.Imaging.ICImagingControl35.dll + + + Form Form1.cs + + @@ -121,6 +154,9 @@ + + + @@ -128,4 +164,10 @@ + + copy /Y $(ProjectDir)config.xml $(TargetDir) +copy /Y $(ProjectDir)tcc_srgb.csv $(TargetDir) +copy /Y $(ProjectDir)tcc_xyz.csv $(TargetDir) + + \ No newline at end of file diff --git a/TIASshot/config.xml b/TIASshot/config.xml new file mode 100644 index 0000000..d954adb --- /dev/null +++ b/TIASshot/config.xml @@ -0,0 +1,47 @@ + + + + 60.0 + 1.5 + 2.62 + 1.72 + 1.70 + + + 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 + + diff --git a/TIASshot/tcc_srgb.csv b/TIASshot/tcc_srgb.csv new file mode 100644 index 0000000..b318e99 --- /dev/null +++ b/TIASshot/tcc_srgb.csv @@ -0,0 +1,24 @@ +96.32301186,112.325025,186.5234201 +82.31324128,80.29383453,171.0852676 +69.47756159,67.16590374,155.6972853 +71.52204285,67.2126815,136.575252 +103.7045379,97.4188714,182.421585 +112.678945,98.23062006,159.3917196 +75.19966729,70.73924383,108.9777609 +137.6863611,135.756064,162.3761344 +91.2658171,84.15017164,104.4524031 +110.8317244,110.9934467,170.9525427 +132.4354009,152.3878419,182.0816559 +94.29666722,122.4487474,176.0173265 +221.4916807,220.074711,228.5409966 +147.9792819,146.040708,150.1114852 +126.4952424,124.0838434,126.7130378 +105.4254531,101.7574962,101.6174637 +78.25650015,76.29487472,76.27829234 +57.73896828,59.32623693,61.5794928 +154.1996749,111.5542849,71.67241514 +87.89371377,156.8409797,98.50080106 +74.25254109,80.30805345,191.4754494 +92.41751804,191.494394,216.1244595 +148.7973681,106.2075592,194.6932888 +194.2086666,161.0512812,84.51280612 diff --git a/TIASshot/tcc_xyz.csv b/TIASshot/tcc_xyz.csv new file mode 100644 index 0000000..2d5a9f1 --- /dev/null +++ b/TIASshot/tcc_xyz.csv @@ -0,0 +1,24 @@ +28.2789,22.9865,14.0906 +21.2022,15.0427,9.8312 +16.7302,11.4927,7.0447 +13.3976,9.7714,7.2293 +26.1415,19.5921,15.4218 +21.6865,17.3528,17.7344 +9.798,8.2236,7.7633 +28.248,27.0529,27.6584 +10.8197,10.0816,11.3333 +25.2779,21.1375,17.7378 +34.7168,34.1732,26.7522 +26.9003,24.0451,13.8827 +70.7262,72.9651,79.1055 +28.159,29.1665,32.1535 +19.6782,20.4204,22.8107 +12.7046,13.27,15.3711 +6.9748,7.3023,8.2978 +4.2878,4.4695,4.6049 +14.2072,15.1766,32.8394 +18.8159,27.3546,13.4973 +25.6995,17.3961,8.5264 +48.922,52.8105,17.8355 +32.9529,24.0451,31.2531 +26.1447,31.2827,55.8215 diff --git "a/\350\211\262\345\244\211\346\217\233 XYZ2sRGB 17\346\254\241\345\205\203\345\244\211\346\217\233.xlsx" "b/\350\211\262\345\244\211\346\217\233 XYZ2sRGB 17\346\254\241\345\205\203\345\244\211\346\217\233.xlsx" new file mode 100644 index 0000000..cf50957 --- /dev/null +++ "b/\350\211\262\345\244\211\346\217\233 XYZ2sRGB 17\346\254\241\345\205\203\345\244\211\346\217\233.xlsx" Binary files differ