using System;
using System.Collections.Concurrent;
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;
FrameSnapSink _snapSinkRef;
/// <summary>
/// IScamコンストラクタ
/// </summary>
/// <param name="form"></param>
/// <param name="ic"></param>
public IScam(Form1 form, ICImagingControl ic, string deviceName) : base(form) {
_ic = ic;
DeviceName = deviceName;
}
/// <summary>
/// IScam接続
/// </summary>
/// <returns></returns>
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;
//}
_roi = new Rect(
Config.GetInt($"{DeviceName}/ROI/X"),
Config.GetInt($"{DeviceName}/ROI/Y"),
Config.GetInt($"{DeviceName}/ROI/W"),
Config.GetInt($"{DeviceName}/ROI/H"));
_queueSink = new FrameQueueSink(Retrieve, MediaSubtypes.RGB24, 5);
_snapSink = new FrameSnapSink(MediaSubtypes.RGB24);
_ic.Sink = _queueSink;
DeviceName = _ic.DeviceCurrent.Name;
SetInfo("カメラ型番", DeviceName);
SerialNumber = _ic.DeviceCurrent.GetSerialNumber();
SetInfo("カメラSN", SerialNumber);
_brightness = _ic.VCDPropertyItems.Find<VCDRangeProperty>(VCDGUIDs.VCDID_Brightness, VCDGUIDs.VCDElement_Value);
_gain = _ic.VCDPropertyItems.Find<VCDRangeProperty>(VCDGUIDs.VCDID_Gain, VCDGUIDs.VCDElement_Value);
_exposure = _ic.VCDPropertyItems.Find<VCDRangeProperty>(VCDGUIDs.VCDID_Exposure, VCDGUIDs.VCDElement_Value);
_gamma = _ic.VCDPropertyItems.Find<VCDRangeProperty>(VCDGUIDs.VCDID_Gamma, VCDGUIDs.VCDElement_Value);
_whiteBalanceBlue = _ic.VCDPropertyItems.Find<VCDRangeProperty>(VCDGUIDs.VCDID_WhiteBalance, VCDGUIDs.VCDElement_WhiteBalanceBlue);
_whiteBalanceGreen = _ic.VCDPropertyItems.Find<VCDRangeProperty>(VCDGUIDs.VCDID_WhiteBalance, VCDGUIDs.VCDElement_WhiteBalanceGreen);
_whiteBalanceRed = _ic.VCDPropertyItems.Find<VCDRangeProperty>(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;
}
/// <summary>
/// IScam切断
/// </summary>
public override void Disconnect() {
_ic.LiveStop();
}
/// <summary>
/// フレーム取得処理
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
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_full = img.T()) {
using (var imgt = new Mat(imgt_full, _roi)) {
// ShowImage は入力 Bitmap を読むだけ(表示用には内部でコピーを生成する)ため,
// 呼び出し側で所有権を持ち,using で確実に Dispose する
using (var frameBmp = imgt.ToBitmap()) {
_form.ShowImage(frameBmp);
}
ProcessCalibrationFrame(imgt);
}
}
}
return FrameQueuedResult.ReQueue;
}
/// <summary>
/// ゲイン/ホワイトバランスの反映(IScam 固有)
/// </summary>
/// <param name="whitePatch">白パッチの計測値</param>
protected override void ApplyWhiteBalance(Scalar whitePatch) {
_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")));
}
/// <summary>
/// 校正収束時のホワイトバランス値ログ出力(IScam 固有)
/// CalcTcc の前に呼ばれ,元コードの Debug.WriteLine 順序を再現する.
/// </summary>
protected override void LogCalibrationConverged() {
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}");
}
/// <summary>
/// 校正完了時のカメラ情報記録(IScam 固有)
/// CalcTcc の後に呼ばれ,SetInfo で各値を保存する.
/// </summary>
/// <param name="imgt">処理対象 Mat</param>
protected override void RecordCalibrationInfo(Mat imgt) {
SetInfo("Exposure(ms)", $"{(float)_exposure.Value / 10}");
SetInfo("Brightness", $"{_brightness.Value}");
SetInfo("Gamma", $"{_gamma.Value}");
SetInfo("Gain", $"{_gain.Value}");
SetInfo("Gain B", $"{_whiteBalanceBlue.Value}");
SetInfo("Gain G", $"{_whiteBalanceGreen.Value}");
SetInfo("Gain R", $"{_whiteBalanceRed.Value}");
SetInfo("画像幅", $"{imgt.Width}");
SetInfo("画像高さ", $"{imgt.Height}");
}
/// <summary>
/// 撮影
/// </summary>
/// <param name="numImages"></param>
/// <param name="interval"></param>
protected override void Shot(int numImages = 1, int interval = 0) {
_ic.LiveStop();
_ic.Sink = _snapSink;
_ic.LiveStart();
_snapSinkRef = _ic.Sink as FrameSnapSink;
_shots = new BlockingCollection<Mat>();
_snapSinkRef.SnapSingle(TimeSpan.FromSeconds(5)); // 最初のフレームを捨てる
var thread = new Thread(() => SaveThread(numImages));
thread.Start();
RunShotLoop(numImages, interval);
_ic.LiveStop();
_ic.Sink = _queueSink;
_ic.LiveStart();
}
/// <summary>
/// 撮影開始処理(IScam 固有): Shot 内でインライン実行済みのため空実装
/// </summary>
protected override void BeginShot() { }
/// <summary>
/// 1 フレーム取得(IScam 固有)
/// </summary>
/// <returns>撮影した Mat(呼び出し元が _shots に追加する)</returns>
protected override Mat CaptureFrame() {
var buffer = _snapSinkRef.SnapSingle(TimeSpan.FromSeconds(5));
using (Mat img = Mat.FromPixelData(buffer.FrameType.Height, buffer.FrameType.Width, MatType.CV_8UC3, buffer.GetIntPtr())) {
using (Mat imgt_full = img.T()) {
return new Mat(imgt_full, _roi);
}
}
}
/// <summary>
/// 撮影終了処理(IScam 固有): Shot 内でインライン実行済みのため空実装
/// </summary>
protected override void EndShot() { }
#if WHITEBOARD
// ---------------------------------------------------------------
// 白板撮影(WHITEBOARD 限定・照明均一性評価用)
// ---------------------------------------------------------------
// Color Enhancement プロパティの GUID(DFK23UX249.xml に記載)
private static readonly Guid ColorEnhancementGuid = new Guid("{3A3A8F77-6440-46CC-940A-8752B02E6C29}");
/// <summary>
/// 白板撮影を実行する(WHITEBOARD 限定).
/// チャート検出・WB 自動調整・TCC 色補正を一切行わず,カメラ素の RGB を保存する.
/// 校正未完了でも撮影可能.
/// </summary>
/// <param name="numImages">撮影枚数</param>
public override void ShotWhiteBoard(int numImages) {
var saveFolder = IoUtil.CreateSaveFolder("白板");
// Color Enhancement を OFF にする(DFK23UX249 専用プロパティ)
bool colorEnhancementDisabled = TryDisableColorEnhancement();
if (!colorEnhancementDisabled) {
Debug.WriteLine("[WhiteBoard] Color Enhancement の無効化をスキップしました(プロパティが存在しないか非対応)");
}
_ic.LiveStop();
_ic.Sink = _snapSink;
_ic.LiveStart();
var snapRef = _ic.Sink as FrameSnapSink;
try {
// 最初のフレームを捨てる(露出安定化)
snapRef.SnapSingle(TimeSpan.FromSeconds(5));
var statsList = new List<FrameStats>();
int fullWidth = 0;
int fullHeight = 0;
for (int i = 0; i < numImages; i++) {
_form.ShowMessage($"白板撮影 {i + 1} / {numImages} 枚目");
var buffer = snapRef.SnapSingle(TimeSpan.FromSeconds(5));
Mat roiFrame = null;
try {
using (Mat img = Mat.FromPixelData(
buffer.FrameType.Height,
buffer.FrameType.Width,
MatType.CV_8UC3,
buffer.GetIntPtr())) {
using (Mat imgt_full = img.T()) {
// フルフレーム(クロップなし)を保存(ビネット確認用)
var fullFilename = $"WhiteBoard_full_{i + 1:0000}.png";
Cv2.ImWrite(Path.Combine(saveFolder, fullFilename), imgt_full);
// フルフレームサイズを記録(全フレームで同値)
fullWidth = imgt_full.Width;
fullHeight = imgt_full.Height;
// ROI 切り出し(通常撮影と同様)
roiFrame = new Mat(imgt_full, _roi).Clone();
}
}
// ROI 画像(PNG)として保存(sRGB 変換なし)
var filename = $"WhiteBoard_{i + 1:0000}.png";
Cv2.ImWrite(Path.Combine(saveFolder, filename), roiFrame);
// 飽和統計の算出(ROI のみ)
var stats = ImageStats.Calc(roiFrame);
statsList.Add(stats);
Debug.WriteLine(
$"[WhiteBoard] frame={i + 1} saturated={stats.SaturatedRatio:P2} " +
$"p99={stats.P99:F1} meanB={stats.MeanB:F1} meanG={stats.MeanG:F1} meanR={stats.MeanR:F1}");
} finally {
if (roiFrame != null) roiFrame.Dispose();
}
}
// JSON サイドカー出力
var jsonMeta = BuildWhiteBoardMeta(colorEnhancementDisabled, numImages, fullWidth, fullHeight);
var jsonStr = ImageStats.BuildJson(jsonMeta, statsList);
File.WriteAllText(
Path.Combine(saveFolder, "whiteboard_meta.json"),
jsonStr,
new System.Text.UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
_form.ShowMessage($"白板撮影終了({numImages} 枚): {saveFolder}");
Debug.WriteLine($"[WhiteBoard] 保存完了: {saveFolder}");
} catch (Exception ex) {
Debug.WriteLine($"[WhiteBoard] 撮影エラー: {ex.Message}");
_form.ShowMessage($"白板撮影エラー: {ex.Message}");
throw;
} finally {
_ic.LiveStop();
_ic.Sink = _queueSink;
_ic.LiveStart();
}
}
/// <summary>
/// Color Enhancement を無効化する.
/// プロパティが存在しない機種では安全にスキップする(GUIDE_09: 空 catch 禁止).
/// </summary>
/// <returns>無効化に成功した場合は true,プロパティ未存在でスキップした場合は false</returns>
private bool TryDisableColorEnhancement() {
VCDSwitchProperty prop = null;
try {
prop = _ic.VCDPropertyItems.Find<VCDSwitchProperty>(
ColorEnhancementGuid,
VCDGUIDs.VCDElement_Value);
} catch (Exception ex) {
// プロパティが存在しない機種では Find が例外を投げる場合がある
Debug.WriteLine($"[WhiteBoard] Color Enhancement プロパティの取得に失敗(スキップ): {ex.Message}");
return false;
}
if (prop == null) {
Debug.WriteLine("[WhiteBoard] Color Enhancement プロパティが存在しません(スキップ)");
return false;
}
prop.Switch = false;
Debug.WriteLine("[WhiteBoard] Color Enhancement を OFF にしました");
return true;
}
/// <summary>
/// JSON サイドカー用の撮影条件メタ辞書を構築する.
/// </summary>
/// <param name="colorEnhancementOff">Color Enhancement を無効化した場合は true</param>
/// <param name="numImages">撮影枚数</param>
/// <param name="fullWidth">フルフレーム(転置後)の幅(未取得時は 0 を渡す)</param>
/// <param name="fullHeight">フルフレーム(転置後)の高さ(未取得時は 0 を渡す)</param>
private Dictionary<string, object> BuildWhiteBoardMeta(bool colorEnhancementOff, int numImages, int fullWidth, int fullHeight) {
var meta = new Dictionary<string, object>();
meta["capture_datetime"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
meta["device_name"] = DeviceName ?? "Unknown";
meta["serial_number"] = SerialNumber ?? "Unknown";
meta["roi_x"] = _roi.X;
meta["roi_y"] = _roi.Y;
meta["roi_width"] = _roi.Width;
meta["roi_height"] = _roi.Height;
meta["num_frames"] = numImages;
meta["full_frame_saved"] = true;
if (fullWidth > 0)
meta["full_width"] = fullWidth;
if (fullHeight > 0)
meta["full_height"] = fullHeight;
// カメラプロパティ(デバイス状態ファイルの固定値を読み取り)
if (_exposure != null)
meta["exposure_ms"] = (double)_exposure.Value / 10.0;
if (_gain != null)
meta["gain"] = (double)_gain.Value;
if (_brightness != null)
meta["brightness"] = (int)_brightness.Value;
if (_gamma != null)
meta["gamma"] = (int)_gamma.Value;
if (_whiteBalanceBlue != null)
meta["wb_blue"] = (int)_whiteBalanceBlue.Value;
if (_whiteBalanceGreen != null)
meta["wb_green"] = (int)_whiteBalanceGreen.Value;
if (_whiteBalanceRed != null)
meta["wb_red"] = (int)_whiteBalanceRed.Value;
meta["color_enhancement_enabled"] = !colorEnhancementOff;
meta["color_enhancement_note"] = colorEnhancementOff
? "Disabled before capture"
: "Not available (skipped)";
// 固定状態(device-state ファイルから既知の設定を明示)
meta["gamma_setting"] = "100 (linear, scale: 100=1.0)";
meta["tone_mapping"] = "OFF";
meta["denoise"] = "0 (OFF)";
meta["sharpness"] = "0 (OFF)";
meta["color_matrix"] = "N/A (ImagingSource: Color Enhancement used instead)";
meta["shading_correction"] = "未補正(LSC 保留)";
meta["shading_correction_note"] = "No flat-field/LSC correction applied. " +
"Measured uniformity includes lens vignetting.";
return meta;
}
#endif
}
}