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 System.Media;
using OpenCvSharp;
namespace TIASshot {
internal abstract class CameraBase {
// インターフェース
public abstract bool Connect();
public abstract void Disconnect();
protected abstract void Shot(int numImages=1, int interval=0);
// テンプレートメソッド用抽象メソッド(プレビュー/校正処理)
protected abstract void ApplyWhiteBalance(Scalar whitePatch);
protected abstract void LogCalibrationConverged();
protected abstract void RecordCalibrationInfo(Mat imgt);
// テンプレートメソッド用抽象メソッド(撮影処理)
protected abstract void BeginShot();
protected abstract Mat CaptureFrame();
protected abstract void EndShot();
// プロパティ
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<Mat> _chartMasks = new List<Mat>();
protected string _saveFolder = "";
protected List<Mat> _shots = new List<Mat>();
protected Dictionary<string, string> _ShotInfo = new Dictionary<string, string>(); // 設定値保存用
protected Rect _roi;
// プライベートメンバ
readonly float UpdateRate;
// 色補正処理(TCC 変換行列の計算・色変換)
readonly ColorCorrector _colorCorrector;
// チャート検出処理(ARマーカー検出・マスク生成)
readonly ChartDetector _chartDetector;
/// <summary>
/// カメラの基本クラス
/// </summary>
public CameraBase(Form1 form) {
_form = form;
UpdateRate = Config.GetFloat("Calib/UpdateRate");
var tccSrgb = IoUtil.LoadMatFromCsv(Config.GetString("File/TccSrgbRef"));
var tccXyz = IoUtil.LoadMatFromCsv(Config.GetString("File/TccXyzRef"));
_colorCorrector = new ColorCorrector(tccSrgb, tccXyz);
_chartDetector = new ChartDetector(_form.ShowMessage);
}
/// <summary>
/// 起動チェック
/// </summary>
/// <returns></returns>
protected bool BootCheck() {
if (!Config.IsLoaded()) {
ErrorMsg = "設定ファイル(Config.xml)の読み込みに失敗しました.\r\n終了します.";
return false;
}
if (!_colorCorrector.SrgbRefLoaded) {
ErrorMsg = $"ファイル({Config.GetString("File/TccSrgbRef")})の読み込みに失敗しました.\r\n終了します.";
return false;
}
if (!_colorCorrector.XyzRefLoaded) {
ErrorMsg = $"ファイル({Config.GetString("File/TccXyzRef")})の読み込みに失敗しました.\r\n終了します.";
return false;
}
return true;
}
/// <summary>
/// 画像撮影1枚
/// </summary>
public void ShotOne() {
SetSaveFolder(_form.GetDataName());
SetInfo("データ名", _form.GetDataName());
SetInfo("撮影枚数", "1");
Shot();
IoUtil.PlaySound(Config.GetString("Sound/OneShotDone"));
_form.ShowMessage("撮影終了");
}
/// <summary>
/// 複数画像撮影
/// </summary>
public void ShotMulti() {
SetSaveFolder(_form.GetDataName());
SetInfo("データ名", _form.GetDataName());
SetInfo("撮影枚数", $"{_form.GetNumMultiShots()}");
SetInfo("撮影間隔(ms)", $"{_form.GetMultiShotsInterval()}");
Shot(_form.GetNumMultiShots(), _form.GetMultiShotsInterval());
IoUtil.PlaySound(Config.GetString("Sound/MultiShotDone"));
_form.ShowMessage("撮影終了");
}
/// <summary>
/// 画像保存処理
/// </summary>
/// <param name="img"></param>
/// <param name="idx"></param>
protected void SaveImages(Mat img, int idx) {
var filename = Config.GetString("File/RgbImage");
filename = filename.Replace("{NO}", $"{idx + 1:0000}");
Cv2.ImWrite(Path.Combine(_saveFolder, filename), img);
foreach (var channel in _colorCorrector.Channels) {
using (var converted = _colorCorrector.ConvertImage(img, channel)) {
filename = GetFilenameWithChannel("File/SrgbImage", channel);
filename = filename.Replace("{NO}", $"{idx + 1:0000}");
Cv2.ImWrite(Path.Combine(_saveFolder, filename), converted);
}
}
}
/// <summary>
/// 画像保存スレッド処理
/// </summary>
/// <param name="numImages"></param>
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);
}
/// <summary>
/// 校正フレーム処理の共通骨格(テンプレートメソッド)
/// imgt が用意された後の校正/検出フローを担う.
/// ゲイン反映(ApplyWhiteBalance)・収束時ログ出力(LogCalibrationConverged)・
/// 校正情報記録(RecordCalibrationInfo)は派生クラスに委譲する.
/// 呼び出し順序は元コードに合わせ「LogCalibrationConverged → CalcTcc → RecordCalibrationInfo」.
/// </summary>
/// <param name="imgt">処理対象 Mat(前処理済み)</param>
protected void ProcessCalibrationFrame(Mat 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("Calib/UpdateInterval") == 0) {
ApplyWhiteBalance(whitePatch);
}
_calibrating--;
if (_calibrating == 0) {
LogCalibrationConverged();
CalcTcc(imgt);
RecordCalibrationInfo(imgt);
}
}
if (!_calibrated && _calibrating == 0) {
DetectChart(imgt);
}
}
/// <summary>
/// 撮影ループの共通骨格(テンプレートメソッド)
/// CSV 書き込み・ループ・タイミング制御を担う.
/// フレーム取得(CaptureFrame)は派生クラスに委譲する.
/// BeginShot/EndShot は呼び出し元(派生クラスの Shot)で前後に実行すること.
/// </summary>
/// <param name="numImages">撮影枚数</param>
/// <param name="interval">撮影間隔(ms)</param>
protected void RunShotLoop(int numImages, int interval) {
var filename = Config.GetString("File/Info");
using (var csv = new StreamWriter(Path.Combine(_saveFolder, filename))) {
WriteInfo(csv);
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} 枚目");
_shots.Add(CaptureFrame());
while (watch.ElapsedMilliseconds < interval * (i + 1) && i < numImages - 1) {
Thread.Sleep(1);
}
}
}
}
/// <summary>
/// チャートの検出
/// </summary>
/// <param name="img"></param>
protected void DetectChart(Mat img) {
// チャート検出・マスク生成は ChartDetector に委譲する
var masks = _chartDetector.DetectChart(img);
if (masks == null) return;
_chartMasks.Clear();
_chartMasks.AddRange(masks);
_calibrating = Config.GetInt("Calib/Frames");
}
/// <summary>
/// ファイル名にチャネル数を追加する
/// </summary>
/// <param name="config"></param>
/// <param name="channel"></param>
/// <returns></returns>
private string GetFilenameWithChannel(string config, int channel) {
var filename = Config.GetString(config);
filename = filename.Replace("{CN}", $"{channel:00}");
return filename;
}
/// <summary>
/// チャートに基づく変換行列の計算
/// </summary>
/// <param name="img"></param>
protected void CalcTcc(Mat img) {
// 変換行列の計算
var tccRgb = GetTccRgb(img);
var imgRois = GetTccRoisImage(img);
// データ保存
SetSaveFolder("校正");
SetInfo("校正データ", _saveFolder);
Cv2.ImWrite(Path.Combine(_saveFolder, Config.GetString("File/TccImage")), img);
Cv2.ImWrite(Path.Combine(_saveFolder, Config.GetString("File/TccRoisImage")), imgRois);
// 変換行列の計算・保存は ColorCorrector に委譲する
_colorCorrector.CalcTcc(tccRgb, _saveFolder, IoUtil.SaveMatToCsv);
IoUtil.PlaySound(Config.GetString("Sound/CalibDone"));
_form.ShowMessage("自動校正完了");
_form.EnableShots();
_calibrated = true;
}
/// <summary>
/// 画像からチャートのRGB値算出
/// </summary>
/// <param name="img"></param>
/// <returns></returns>
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<double>(i, 0) = rgb.Val0; // Blue
arrRGB.At<double>(i, 1) = rgb.Val1; // Green
arrRGB.At<double>(i, 2) = rgb.Val2; // Red
}
return arrRGB;
}
/// <summary>
/// TCCのROI画像を取得
/// </summary>
/// <param name="img"></param>
/// <returns></returns>
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;
}
/// <summary>
/// ゲイン値の更新比率計算
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 保存フォルダの取得
/// </summary>
/// <param name="note"></param>
protected void SetSaveFolder(string note) {
_saveFolder = IoUtil.CreateSaveFolder(note);
}
/// <summary>
/// 設定値を保存する
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
protected void SetInfo(string key, string value) {
if (_ShotInfo.ContainsKey(key)) {
_ShotInfo[key] = value;
} else {
_ShotInfo.Add(key, value);
}
}
/// <summary>
/// 設定値をCSVファイルに書き込む
/// </summary>
/// <param name="sw"></param>
protected void WriteInfo(StreamWriter sw) {
IoUtil.WriteInfo(sw, _ShotInfo);
}
}
}