diff --git a/TIASshot/CameraBase.cs b/TIASshot/CameraBase.cs new file mode 100644 index 0000000..b3703f8 --- /dev/null +++ b/TIASshot/CameraBase.cs @@ -0,0 +1,352 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using OpenCvSharp; +using OpenCvSharp.Aruco; + +namespace TIASshot { + internal class CameraBase { + // 派生クラスで使用するメンバ + protected Form1 _form; + protected int _calibrating = 0; + protected bool _calibrated = false; + protected Bitmap[] _bmps = new Bitmap[2]; + protected int _bmpIndex = 0; + protected readonly float RefRGB; + readonly float UpdateRate; + protected readonly Mat TCC_SRGB; + protected readonly Mat TCC_XYZ; + + public string DeviceName { get; protected set; } = "Unknown"; + public string SerialNumber { get; protected set; } + public string ErrorMsg { get; protected set; } + + // プライベートメンバ + readonly Dictionary ARDict = CvAruco.GetPredefinedDictionary(PredefinedDictionaryName.Dict4X4_50); + readonly Point2f[] PointsDst40 = new Point2f[] { + new Point2f(345, 130),new Point2f(465, 130),new Point2f(465, 250),new Point2f(345, 250), + }; + readonly Point2f[] PointsDst41 = new Point2f[]{ + new Point2f(345, 1200), new Point2f(465, 1200), new Point2f(465, 1320), new Point2f(345, 1320), + }; + int _detectionCount = 0; + Point2f _lastPosition = new Point2f(0, 0); + protected List _chartMasks = new List(); + Mat _tccRois; // TCCのROI検出結果画像 + protected Mat _convRGB2SRGB; + + /// + /// カメラの基本クラス + /// + public CameraBase(Form1 form) { + _form = form; + Config.Load(); + RefRGB = Config.GetFloat("ReferenceValue"); + UpdateRate = Config.GetFloat("UpdateRate"); + TCC_SRGB = LoadMatFromCsv(Config.GetString("TccSrgbTableFile")); + TCC_XYZ = LoadMatFromCsv(Config.GetString("TccXyzTableFile")); + } + + /// + /// 起動チェック + /// + /// + protected bool BootCheck() { + + if (!Config.IsLoaded()) { + ErrorMsg = "設定ファイル(Config.xml)の読み込みに失敗しました.\r\n終了します."; + return false; + } + if (TCC_SRGB is null) { + ErrorMsg = $"ファイル({Config.GetString("TccSrgbTableFile")})の読み込みに失敗しました.\r\n終了します."; + return false; + } + if (TCC_XYZ is null) { + ErrorMsg = $"ファイル({Config.GetString("TccXyzTableFile")})の読み込みに失敗しました.\r\n終了します."; + return false; + } + return 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("ChartSetCriteria")) { + _detectionCount++; + } else { + _detectionCount = 0; + } + _lastPosition = position; + if (_detectionCount < Config.GetInt("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; + _tccRois = img.Clone(); + 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(_tccRois.Size(), MatType.CV_8U); + Cv2.WarpPerspective(mask, maskF, matH, maskF.Size()); + _chartMasks.Add(maskF); + _tccRois.SetTo(new Scalar(0, 200, 0), maskF); + } + } + + _form.ShowMessage("舌診チャート検出 校正中"); + _calibrating = Config.GetInt("CalibrationFrames"); + } + + /// + /// チャートに基づく変換行列の計算 + /// + /// + protected void CalcTcc(Mat img) { + // 変換行列の計算 + var tccRgb = GetTccRgb(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}"); + + // データ保存 + var folder = GetSaveFolder("校正"); + Cv2.ImWrite(Path.Combine(folder, Config.GetString("TccSaveFile")), img); + Cv2.ImWrite(Path.Combine(folder, Config.GetString("TccRoisFile")), _tccRois); + SaveMatToCsv(Path.Combine(folder, Config.GetString("ConvSrgbSaveFile")), _convRGB2SRGB); + SaveMatToCsv(Path.Combine(folder, Config.GetString("ConvXyzSaveFile")), _convRGB2XYZ); + SaveMatToCsv(Path.Combine(folder, Config.GetString("ConvSrgb2XyzSaveFile")), convSrgb2Xyz); + + _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; + arrRGB.At(i, 1) = rgb.Val1; + arrRGB.At(i, 2) = rgb.Val2; + } + return arrRGB; + } + + /// + /// 変換行列算出 + /// + /// 画像 + /// 変換目標の24x3行列 + private Mat CalcConvertMatrix(Mat src, Mat target) { + var extended = ExtendMat(src); + Mat convMat = new Mat(17, 3, MatType.CV_64FC1); + Cv2.Solve(extended, target, convMat, DecompTypes.SVD); + return convMat; + } + + /// + /// 色変換 + /// + /// + /// + /// + private Mat ConvertColor(Mat src, Mat convMat) { + var extended = ExtendMat(src); + var converted = extended * convMat; + return converted.ToMat(); + } + + /// + /// 画像の色変換 + /// + /// + /// + /// 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); + var converted = (extended * conv).ToMat(); + var convertedImage = converted.Reshape(3, src.Height); + return convertedImage; + } + + /// + /// 行列の拡張 3次元→17次元 + /// + /// + /// + private Mat ExtendMat(Mat src) { + if (src.Cols * src.Channels() != 3) return src; + var dst = new Mat(src.Rows, 17, MatType.CV_64FC1); + for (int row = 0; row < src.Rows; row++) { + var b = src.Cols == 1 ? src.At(row, 0)[0] : src.At(row, 0); + var g = src.Cols == 1 ? src.At(row, 0)[1] : src.At(row, 1); + var r = src.Cols == 1 ? src.At(row, 0)[2] : src.At(row, 2); + dst.At(row, 0) = r; + dst.At(row, 1) = g; + dst.At(row, 2) = b; + dst.At(row, 3) = r * g; + dst.At(row, 4) = r * b; + dst.At(row, 5) = g * b; + dst.At(row, 6) = r * r; + dst.At(row, 7) = g * g; + dst.At(row, 8) = b * b; + dst.At(row, 9) = r * r * b; + dst.At(row, 10) = r * r * g; + dst.At(row, 11) = g * g * r; + dst.At(row, 12) = g * g * b; + dst.At(row, 13) = b * b * r; + dst.At(row, 14) = b * b * g; + dst.At(row, 15) = r * g * b; + dst.At(row, 16) = 1.0; + } + 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 string GetSaveFolder(string note) { + var dt = DateTime.Now; + var paths = new List() { + Config.GetString("SaveFolder"), + dt.ToString("yyyy-MM-dd"), + dt.ToString("HH_mm_ss") + $"-{note}", + }; + var path = Path.Combine(paths.ToArray()); + Directory.CreateDirectory(path); + return path; + } + + } +} diff --git a/TIASshot/Form1.cs b/TIASshot/Form1.cs index 061e4e2..5209a97 100644 --- a/TIASshot/Form1.cs +++ b/TIASshot/Form1.cs @@ -22,7 +22,6 @@ private IScam _iscam; private LightSource _light = new LightSource(); private bool _isLightOn = true; - //public ICImagingControl IcCam { get { return icImagingControl1; } } /// /// コンストラクタ @@ -31,7 +30,7 @@ InitializeComponent(); //_lucam = new Lucam(this, picPreview, picDisplay); - _iscam = new IScam(this, icImagingControl1); + _iscam = new IScam(this, icImagingControl1, "DBK33UX178"); var version = Assembly.GetExecutingAssembly().GetName().Version; Text = $"{APP_NAME} ver.{version.Major}.{version.Minor}"; @@ -60,8 +59,8 @@ } - txtDeviceName.Text = icImagingControl1.DeviceCurrent.Name; - txtSerialNo.Text = icImagingControl1.DeviceCurrent.GetSerialNumber(); + txtDeviceName.Text = _iscam.DeviceName; + txtSerialNo.Text = _iscam.SerialNumber; //txtDeviceName.Text = _lucam.DeviceName; //txtSerialNo.Text = _lucam.SerialNumber; txtSaveFolder.Text = Config.GetString("SaveFolder"); @@ -115,6 +114,7 @@ /// /// public void ShowImage(Bitmap bmp) { + // なぜかわからないがここはInvokeなしでworkerスレッドから呼び出しできる //if (InvokeRequired) { // Invoke((MethodInvoker)delegate { ShowImage(bmp); }); // return; @@ -146,6 +146,10 @@ return txtDataName.Text; } + /// + /// マルチショット撮影の枚数取得 + /// + /// public int GetNumMultiShots() { if (InvokeRequired) { return (int)Invoke((MethodInvoker)delegate { GetNumMultiShots(); }); @@ -154,6 +158,10 @@ return 1; } + /// + /// マルチショット撮影の間隔取得 + /// + /// public int GetMultiShotsInterval() { if (InvokeRequired) { return (int)Invoke((MethodInvoker)delegate { GetMultiShotsInterval(); }); @@ -184,6 +192,11 @@ System.Diagnostics.Process.Start(txtSaveFolder.Text); } + /// + /// 光源のON/OFF切り替え + /// + /// + /// private void btnLightSW_Click(object sender, EventArgs e) { if (!_light.IsOpen) return; if (_isLightOn) { diff --git a/TIASshot/IScam.cs b/TIASshot/IScam.cs index 26b5dc1..216f3f1 100644 --- a/TIASshot/IScam.cs +++ b/TIASshot/IScam.cs @@ -11,68 +11,119 @@ using System.Drawing; namespace TIASshot { - internal class IScam { + internal class IScam : CameraBase { ICImagingControl _ic; - Form1 _form; - Bitmap[] _bmps = new Bitmap[2]; - int _bmpIndex = 0; + VCDRangeProperty _brightness; + VCDRangeProperty _gain; + VCDRangeProperty _exposure; + VCDRangeProperty _gamma; + VCDRangeProperty _whiteBalanceBlue; + VCDRangeProperty _whiteBalanceGreen; + VCDRangeProperty _whiteBalanceRed; - public IScam(Form1 form, ICImagingControl ic) { - _form = form; + /// + /// IScamコンストラクタ + /// + /// + /// + public IScam(Form1 form, ICImagingControl ic, string deviceName) : base(form) { _ic = ic; - - Config.Load(); - + DeviceName = deviceName; } + /// + /// IScam接続 + /// + /// public bool Connect() { - if (!_ic.LoadShowSaveDeviceState("lastSelectedDeviceState.xml")) { + if (!BootCheck()) return false; + + //if (!_ic.LoadShowSaveDeviceState("lastSelectedDeviceState.xml")) { + // return false; + //} + //_ic.SaveDeviceStateToFile("DBK33UX178.xml"); + _ic.LoadDeviceStateFromFile($"{DeviceName}.xml", true); + if (!_ic.DeviceValid) { + MessageBox.Show("IScamの設定ファイルが見つかりません。", Form1.APP_NAME, + MessageBoxButtons.OK, MessageBoxIcon.Warning); return false; } - _ic.Sink = new TIS.Imaging.FrameQueueSink(Retrieve, MediaSubtypes.RGB24, 5); - _ic.LiveStart(); + _ic.Sink = new TIS.Imaging.FrameQueueSink(Retrieve, MediaSubtypes.RGB24, 5); + 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); + _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}"); + 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 void Disconnect() { _ic.LiveStop(); } + /// + /// フレーム取得処理 + /// + /// + /// private FrameQueuedResult Retrieve(IFrameQueueBuffer buffer) { - //var image = buffer.CreateBitmapWrap(); 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("CalibrationUpdateInterval") == 0) { - // _snap.GainBlue *= GetRatio((float)whitePatch.Val0, Config.GetFloat("ReferenceB")); - // _snap.GainGrn1 *= GetRatio((float)whitePatch.Val1, Config.GetFloat("ReferenceG")); - // _snap.GainGrn2 = _snap.GainGrn1; - // _snap.GainRed *= GetRatio((float)whitePatch.Val2, Config.GetFloat("ReferenceR")); - // SetCameraParam(); - // } - // _calibrating--; - // if (_calibrating == 0) { - // CalcTcc(imgt); - // } - //} + 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"))); + } + _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}"); - //if (!_calibrated && _calibrating == 0) { - // DetectChart(imgt); - //} + CalcTcc(imgt); + } + } + + if (!_calibrated && _calibrating == 0) { + DetectChart(imgt); + } } } _form.ShowImage(_bmps[_bmpIndex]); _bmpIndex = (_bmpIndex + 1) % 2; if (_bmps[_bmpIndex] != null) _bmps[_bmpIndex].Dispose(); - //_form.ShowImage(image); return FrameQueuedResult.ReQueue; } } diff --git a/TIASshot/Lucam.cs b/TIASshot/Lucam.cs index 1d218c1..7b22873 100644 --- a/TIASshot/Lucam.cs +++ b/TIASshot/Lucam.cs @@ -27,64 +27,27 @@ /// /// Lumeneraカメラクラス /// - internal class Lucam { + internal class Lucam : CameraBase { // 光源の輝度設定 // 連続撮影のマルチスレッド化 // GUI更新はタイマー使用に変更 - public string DeviceName { get; private set; } = "Unknown"; - public string SerialNumber { get; private set; } - public string ErrorMsg { get; private set; } - - 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 float RefRGB; - readonly float UpdateRate; - readonly Mat TCC_SRGB; - readonly Mat TCC_XYZ; - IntPtr _hCam = IntPtr.Zero; - PictureBox _picPreview, _picDisplay; - Form1 _form; + PictureBox _picPreview; bool _isPreview = false; - //bool _check = false; - int _calibrating = 0; - bool _calibrated = false; dll.LucamSnapshot _snap; dll.LucamConversion _convert; dll.LucamRgbPreviewCallback _callbackHandler; int _callbackId; - Bitmap[] _bmps = new Bitmap[2]; - int _bmpIndex = 0; - List _chartMasks = new List(); - int _detectionCount = 0; - Point2f _lastPosition = new Point2f(0, 0); - Mat _convRGB2SRGB; - //Mat _convRGB2XYZ; - Mat _tccRois; // TCCのROI検出結果画像 /// /// コンストラクタ /// /// /// - public Lucam(Form1 form, PictureBox preview, PictureBox display) { + public Lucam(Form1 form, PictureBox preview) : base(form) { _picPreview = preview; - _picDisplay = display; - _form = form; - - Config.Load(); - RefRGB = Config.GetFloat("ReferenceValue"); - UpdateRate = Config.GetFloat("UpdateRate"); - - TCC_SRGB = LoadMatFromCsv(Config.GetString("TccSrgbTableFile")); - TCC_XYZ = LoadMatFromCsv(Config.GetString("TccXyzTableFile")); // カメラパラメータの初期値 _snap.BufferLastFrame = false; @@ -120,27 +83,6 @@ } /// - /// 起動チェック - /// - /// - private bool BootCheck() { - - if (!Config.IsLoaded()) { - ErrorMsg = "設定ファイル(Config.xml)の読み込みに失敗しました.\r\n終了します."; - return false; - } - if (TCC_SRGB is null) { - ErrorMsg = $"ファイル({Config.GetString("TccSrgbTableFile")})の読み込みに失敗しました.\r\n終了します."; - return false; - } - if (TCC_XYZ is null) { - ErrorMsg = $"ファイル({Config.GetString("TccXyzTableFile")})の読み込みに失敗しました.\r\n終了します."; - return false; - } - return true; - } - - /// /// カメラ接続 /// /// @@ -206,93 +148,6 @@ } /// - /// ゲイン値の更新比率計算 - /// - /// - /// - private float GetRatio(float value, float target) { - float ratio = target / value; - ratio = (ratio - 1.0f) * (value == 255.0f ? 1.0f : UpdateRate) + 1.0f; - return ratio; - } - - /// - /// チャートの検出 - /// - /// - private 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("ChartSetCriteria")) { - _detectionCount++; - } else { - _detectionCount = 0; - } - _lastPosition = position; - if (_detectionCount < Config.GetInt("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; - _tccRois = img.Clone(); - 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(_tccRois.Size(), MatType.CV_8U); - Cv2.WarpPerspective(mask, maskF, matH, maskF.Size()); - _chartMasks.Add(maskF); - _tccRois.SetTo(new Scalar(0, 200, 0), maskF); - } - } - - _form.ShowMessage("舌診チャート検出 校正中"); - _calibrating = Config.GetInt("CalibrationFrames"); - } - - /// /// プレビューコールバック /// /// @@ -331,75 +186,6 @@ } /// - /// チャートに基づく変換行列の計算 - /// - /// - private void CalcTcc(Mat img) { - // 変換行列の計算 - var tccRgb = GetTccRgb(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}"); - - // データ保存 - var folder = GetSaveFolder("校正"); - Cv2.ImWrite(Path.Combine(folder, Config.GetString("TccSaveFile")), img); - Cv2.ImWrite(Path.Combine(folder, Config.GetString("TccRoisFile")), _tccRois); - SaveMatToCsv(Path.Combine(folder, Config.GetString("ConvSrgbSaveFile")), _convRGB2SRGB); - SaveMatToCsv(Path.Combine(folder, Config.GetString("ConvXyzSaveFile")), _convRGB2XYZ); - SaveMatToCsv(Path.Combine(folder, Config.GetString("ConvSrgb2XyzSaveFile")), convSrgb2Xyz); - - _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; - arrRGB.At(i, 1) = rgb.Val1; - arrRGB.At(i, 2) = rgb.Val2; - } - return arrRGB; - } - - /// - /// 変換行列算出 - /// - /// 画像 - /// 変換目標の24x3行列 - private Mat CalcConvertMatrix(Mat src, Mat target) { - var extended = ExtendMat(src); - Mat convMat = new Mat(17, 3, MatType.CV_64FC1); - Cv2.Solve(extended, target, convMat, DecompTypes.SVD); - return convMat; - } - - /// - /// 色変換 - /// - /// - /// - /// - private Mat ConvertColor(Mat src, Mat convMat) { - var extended = ExtendMat(src); - var converted = extended * convMat; - return converted.ToMat(); - } - - /// /// 画像撮影1枚 /// public void ShotOne() { @@ -493,122 +279,6 @@ Debug.WriteLine($"SetCameraParam Exp {_snap.Exposure} Gain {_snap.Gain} GainBlue {_snap.GainBlue} GainGrn1 {_snap.GainGrn1} GainGrn2 {_snap.GainGrn2} GainRed {_snap.GainRed}"); } - /// - /// 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); - } - } - } - - /// - /// 画像の色変換 - /// - /// - /// - /// 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); - var converted = (extended * conv).ToMat(); - var convertedImage = converted.Reshape(3, src.Height); - return convertedImage; - } - - /// - /// 行列の拡張 3次元→17次元 - /// - /// - /// - private Mat ExtendMat(Mat src) { - if (src.Cols * src.Channels() != 3) return src; - var dst = new Mat(src.Rows, 17, MatType.CV_64FC1); - for (int row = 0; row < src.Rows; row++) { - var b = src.Cols == 1 ? src.At(row, 0)[0] : src.At(row, 0); - var g = src.Cols == 1 ? src.At(row, 0)[1] : src.At(row, 1); - var r = src.Cols == 1 ? src.At(row, 0)[2] : src.At(row, 2); - dst.At(row, 0) = r; - dst.At(row, 1) = g; - dst.At(row, 2) = b; - dst.At(row, 3) = r * g; - dst.At(row, 4) = r * b; - dst.At(row, 5) = g * b; - dst.At(row, 6) = r * r; - dst.At(row, 7) = g * g; - dst.At(row, 8) = b * b; - dst.At(row, 9) = r * r * b; - dst.At(row, 10) = r * r * g; - dst.At(row, 11) = g * g * r; - dst.At(row, 12) = g * g * b; - dst.At(row, 13) = b * b * r; - dst.At(row, 14) = b * b * g; - dst.At(row, 15) = r * g * b; - dst.At(row, 16) = 1.0; - } - return dst; - } - - /// - /// 保存フォルダの取得 - /// - /// - /// - private string GetSaveFolder(string note) { - var dt = DateTime.Now; - var paths = new List() { - Config.GetString("SaveFolder"), - dt.ToString("yyyy-MM-dd"), - dt.ToString("HH_mm_ss") + $"-{note}", - }; - var path = Path.Combine(paths.ToArray()); - Directory.CreateDirectory(path); - return path; - } /// /// カメラ切断 diff --git a/TIASshot/Properties/AssemblyInfo.cs b/TIASshot/Properties/AssemblyInfo.cs index 39323da..9a4a387 100644 --- a/TIASshot/Properties/AssemblyInfo.cs +++ b/TIASshot/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ // ビルド番号 // リビジョン // -[assembly: AssemblyVersion("1.2.0.0")] -[assembly: AssemblyFileVersion("1.2.0.0")] +[assembly: AssemblyVersion("1.3.0.0")] +[assembly: AssemblyFileVersion("1.3.0.0")] diff --git a/TIASshot/TIASshot.csproj b/TIASshot/TIASshot.csproj index e5d1592..068ff70 100644 --- a/TIASshot/TIASshot.csproj +++ b/TIASshot/TIASshot.csproj @@ -111,6 +111,7 @@ + Form diff --git a/TIASshot/config.xml b/TIASshot/config.xml index 88c0b5d..94c325d 100644 --- a/TIASshot/config.xml +++ b/TIASshot/config.xml @@ -5,11 +5,20 @@ 2.62 1.72 1.70 + + -7 + 48 + 240 + 100 + 170 + 75 + 100 + 226 226 226 - 0.5 - 50 + 0.8 + 100 5 5 1000