diff --git a/TIASshot.Tests/ColorCorrectorTests.cs b/TIASshot.Tests/ColorCorrectorTests.cs
index 21ad28f..f06f12e 100644
--- a/TIASshot.Tests/ColorCorrectorTests.cs
+++ b/TIASshot.Tests/ColorCorrectorTests.cs
@@ -1,3 +1,6 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
using NUnit.Framework;
using OpenCvSharp;
@@ -420,5 +423,80 @@
}
}
}
+
+ // ---------------------------------------------------------------
+ // CalcTcc の資源解放テスト(v1.7 の Mat リーク修正を内包することの確認)
+ // ---------------------------------------------------------------
+
+ ///
+ /// CalcTcc: 中間 Mat(辞書に保持しないもの)は呼び出し完了時に解放され,
+ /// 入力 tccRgb(呼び出し側所有)は解放されず,再校正時に旧変換行列が破棄されること.
+ /// v1.7 が塞いだ校正経路の Mat リークを本実装が内包していることを保証する.
+ ///
+ [Test]
+ public void CalcTcc_再校正_中間Matと旧変換行列を解放し入力は保持する() {
+ LoadCalcTccConfig();
+
+ using (var tccSrgb = CreateScaledColorChart(rows: 24, scale: 1.0))
+ using (var tccXyz = CreateScaledColorChart(rows: 24, scale: 0.8))
+ using (var tccRgb = CreateLinearColorChart(rows: 24)) {
+ // channel=4(通常経路)と 17(convOld 特殊経路)の両方を通す
+ var corrector = new ColorCorrector(tccSrgb, tccXyz, new int[] { 4, 17 });
+
+ // 1回目の校正:コールバックに渡る Mat を全て捕捉する
+ var firstSaved = new List>();
+ corrector.CalcTcc(tccRgb, "dummy",
+ (path, mat) => firstSaved.Add(new KeyValuePair(path, mat)));
+
+ // 中間 Mat(ConvRgb2Xyz / ConvRgb2XyzOld / TccSrgb / ConvSrgb2Xyz)は解放済み
+ foreach (var kv in firstSaved) {
+ var name = Path.GetFileName(kv.Key);
+ if (name.Contains("convrgb2xyz") || name.Contains("tccsrgb") || name.Contains("convsrgb2xyz")) {
+ Assert.IsTrue(kv.Value.IsDisposed, $"中間 Mat は解放済みのはず: {name}");
+ }
+ }
+
+ // 入力 tccRgb は ColorCorrector に破棄されない(所有権は呼び出し側)
+ var tccRgbSaved = firstSaved.First(kv => Path.GetFileName(kv.Key).Contains("tccrgb")).Value;
+ Assert.IsFalse(tccRgbSaved.IsDisposed, "入力 tccRgb は破棄されない(所有権は呼び出し側)");
+ Assert.IsFalse(tccRgb.IsDisposed, "入力 tccRgb 本体も生存している");
+
+ // 保持される変換行列(ConvRgb2Srgb, ch4)は 1 回目校正後に生存
+ var keptCh4 = firstSaved.First(kv => {
+ var n = Path.GetFileName(kv.Key);
+ return n.Contains("convrgb2srgb") && n.Contains("04");
+ }).Value;
+ Assert.IsFalse(keptCh4.IsDisposed, "保持中の変換行列は 1 回目校正後に生存している");
+
+ // 2回目の校正:旧エントリ(1回目の保持行列)が破棄される
+ corrector.CalcTcc(tccRgb, "dummy", (path, mat) => { });
+ Assert.IsTrue(keptCh4.IsDisposed, "再校正で古い変換行列(旧エントリ)が破棄される");
+ }
+ }
+
+ ///
+ /// CalcTcc テスト用に,参照される File/* キーを持つ最小 Config を読み込む.
+ /// ファイル名は種類ごとに識別可能な文字列にし,コールバックの path で判別する.
+ ///
+ private static void LoadCalcTccConfig() {
+ const string xml =
+ "" +
+ "" +
+ " tccrgb.csv" +
+ " convrgb2srgb_{CN}.csv" +
+ " convrgb2xyz_{CN}.csv" +
+ " convrgb2xyzold_{CN}.csv" +
+ " tccsrgb_{CN}.csv" +
+ " convsrgb2xyz_{CN}.csv" +
+ "";
+
+ var path = Path.GetTempFileName() + ".xml";
+ File.WriteAllText(path, xml, System.Text.Encoding.UTF8);
+ try {
+ Config.Load(path);
+ } finally {
+ if (File.Exists(path)) File.Delete(path);
+ }
+ }
}
}
diff --git a/TIASshot/Cameras/CameraBase.cs b/TIASshot/Cameras/CameraBase.cs
index e521dc2..d90bce6 100644
--- a/TIASshot/Cameras/CameraBase.cs
+++ b/TIASshot/Cameras/CameraBase.cs
@@ -62,6 +62,8 @@
protected BlockingCollection _shots = new BlockingCollection();
protected Dictionary _ShotInfo = new Dictionary(); // 設定値保存用
protected Rect _roi;
+ protected Thread _saveThread = null; // SaveThreadへの参照を保持(前回完了待機用)
+ protected ManualResetEventSlim _saveThreadReady = new ManualResetEventSlim(false); // SaveThread起動完了の同期
// プライベートメンバ
readonly float UpdateRate;
@@ -142,7 +144,12 @@
Cv2.ImWrite(Path.Combine(_saveFolder, filename), img);
foreach (var channel in _colorCorrector.Channels) {
- using (var converted = _colorCorrector.ConvertImage(img, channel)) {
+ // ConvertImage は src を in-place で CV_64FC3 に変換するため、
+ // 各チャンネルで独立したクローンを作成して元画像を保護する。
+ // 【重要】Clone を作成しないと、同一 Mat を複数チャンネルで変換時に
+ // 2チャンネル目以降が破壊された型(CV_64FC3)を処理してクラッシュする。
+ using (var imgClone = img.Clone())
+ using (var converted = _colorCorrector.ConvertImage(imgClone, channel)) {
filename = GetFilenameWithChannel("File/SrgbImage", channel);
filename = filename.Replace("{NO}", $"{idx + 1:0000}");
Cv2.ImWrite(Path.Combine(_saveFolder, filename), converted);
@@ -155,9 +162,14 @@
///
///
protected void SaveThread(int numImages) {
+ // スレッド起動完了を通知(producer が待機している)
+ _saveThreadReady.Set();
+
int saveCount = 0;
foreach (var img in _shots.GetConsumingEnumerable()) {
- SaveImages(img, saveCount);
+ using (img) {
+ SaveImages(img, saveCount);
+ }
saveCount++;
_form.ShowMessage($"画像{saveCount}/{numImages}枚目保存");
}
@@ -269,18 +281,19 @@
///
protected void CalcTcc(Mat img) {
- // 変換行列の計算
- var tccRgb = GetTccRgb(img);
- var imgRois = GetTccRoisImage(img);
+ // 変換行列の計算(tccRgb・imgRois はここで所有し,使用後に確実に解放する)
+ using (var tccRgb = GetTccRgb(img))
+ using (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);
+ // データ保存
+ 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);
+ // 変換行列の計算・保存は ColorCorrector に委譲する(tccRgb の所有権は渡さない)
+ _colorCorrector.CalcTcc(tccRgb, _saveFolder, IoUtil.SaveMatToCsv);
+ }
IoUtil.PlaySound(Config.GetString("Sound/CalibDone"));
_form.ShowMessage("自動校正完了");
diff --git a/TIASshot/Cameras/IScam.cs b/TIASshot/Cameras/IScam.cs
index 1652a83..da3b09a 100644
--- a/TIASshot/Cameras/IScam.cs
+++ b/TIASshot/Cameras/IScam.cs
@@ -166,12 +166,29 @@
_ic.LiveStart();
_snapSinkRef = _ic.Sink as FrameSnapSink;
+ // 前回の SaveThread が完了するまで待機(低スペックPCでの競合回避)
+ if (_saveThread != null && _saveThread.IsAlive) {
+ _saveThread.Join();
+ }
+
+ // 古い BlockingCollection を破棄してから新しいインスタンスを作成
+ _shots?.Dispose();
_shots = new BlockingCollection();
_snapSinkRef.SnapSingle(TimeSpan.FromSeconds(5)); // 最初のフレームを捨てる
+ // ManualResetEventSlim をリセットして SaveThread の起動完了を待機できるようにする
+ _saveThreadReady.Reset();
+
var thread = new Thread(() => SaveThread(numImages));
+ _saveThread = thread; // 参照を保持(次回の完了待機用)
thread.Start();
+ // SaveThread が起動完了するまで待機(consumer 準備完了を保証)
+ // タイムアウト設定で無限待機を回避(通常は即座に完了するはず)
+ if (!_saveThreadReady.Wait(TimeSpan.FromSeconds(5))) {
+ Debug.WriteLine("警告: SaveThread の起動完了待機がタイムアウトしました");
+ }
+
RunShotLoop(numImages, interval);
_ic.LiveStop();
diff --git a/TIASshot/Cameras/Lucam.cs b/TIASshot/Cameras/Lucam.cs
index ea5b953..961d32d 100644
--- a/TIASshot/Cameras/Lucam.cs
+++ b/TIASshot/Cameras/Lucam.cs
@@ -224,10 +224,27 @@
protected override void Shot(int numImages=1, int interval=0) {
SetSnapParam();
+ // 前回の SaveThread が完了するまで待機(低スペックPCでの競合回避)
+ if (_saveThread != null && _saveThread.IsAlive) {
+ _saveThread.Join();
+ }
+
+ // 古い BlockingCollection を破棄してから新しいインスタンスを作成
+ _shots?.Dispose();
_shots = new BlockingCollection();
+
+ // ManualResetEventSlim をリセットして SaveThread の起動完了を待機できるようにする
+ _saveThreadReady.Reset();
+
var thread = new Thread(() => SaveThread(numImages));
+ _saveThread = thread; // 参照を保持(次回の完了待機用)
thread.Start();
+ // SaveThread が起動完了するまで待機(consumer 準備完了を保証)
+ if (!_saveThreadReady.Wait(TimeSpan.FromSeconds(5))) {
+ Debug.WriteLine("警告: SaveThread の起動完了待機がタイムアウトしました");
+ }
+
BeginShot();
RunShotLoop(numImages, interval);
EndShot();
diff --git a/TIASshot/ColorCorrection/ColorCorrector.cs b/TIASshot/ColorCorrection/ColorCorrector.cs
index 16388b0..eadebea 100644
--- a/TIASshot/ColorCorrection/ColorCorrector.cs
+++ b/TIASshot/ColorCorrection/ColorCorrector.cs
@@ -91,34 +91,45 @@
saveMatToCsv(Path.Combine(saveFolder, Config.GetString("File/TccRgbValues")), tccRgb);
+ // 再校正時,古い変換行列のネイティブ資源を解放してから作り直す(Mat リーク防止)
+ foreach (var mat in _convRGB2SRGB.Values) {
+ mat.Dispose();
+ }
_convRGB2SRGB.Clear();
+
foreach (var channel in ChannelList) {
var convRGB2SRGB = CalcConvertMatrix(tccRgb, TCC_SRGB, channel);
saveMatToCsv(Path.Combine(saveFolder, GetFilenameWithChannel("File/ConvRgb2Srgb", channel)), convRGB2SRGB);
- var convRGB2XYZ = CalcConvertMatrix(tccRgb, TCC_XYZ, channel);
- saveMatToCsv(Path.Combine(saveFolder, GetFilenameWithChannel("File/ConvRgb2Xyz", channel)), convRGB2XYZ);
- if (channel == 17) {
- var convOld = new Mat(convRGB2XYZ.Size(), MatType.CV_64FC1);
- for (var row = 0; row < convRGB2XYZ.Rows; row++) {
- var rowFrom = (row + 1) % convRGB2XYZ.Rows; // 1行ずらす
- for (var col = 0; col < convRGB2XYZ.Cols; col++) {
- convOld.At(row, col) = convRGB2XYZ.At(rowFrom, col);
+ // 以降の中間 Mat は辞書に保持しないため,使用後に確実に解放する
+ using (var convRGB2XYZ = CalcConvertMatrix(tccRgb, TCC_XYZ, channel)) {
+ saveMatToCsv(Path.Combine(saveFolder, GetFilenameWithChannel("File/ConvRgb2Xyz", channel)), convRGB2XYZ);
+ if (channel == 17) {
+ using (var convOld = new Mat(convRGB2XYZ.Size(), MatType.CV_64FC1)) {
+ for (var row = 0; row < convRGB2XYZ.Rows; row++) {
+ var rowFrom = (row + 1) % convRGB2XYZ.Rows; // 1行ずらす
+ for (var col = 0; col < convRGB2XYZ.Cols; col++) {
+ convOld.At(row, col) = convRGB2XYZ.At(rowFrom, col);
+ }
+ }
+ saveMatToCsv(Path.Combine(saveFolder, GetFilenameWithChannel("File/ConvRgb2XyzOld", channel)), convOld);
}
}
- saveMatToCsv(Path.Combine(saveFolder, GetFilenameWithChannel("File/ConvRgb2XyzOld", channel)), convOld);
}
- var tccSrgb = ConvertColor(tccRgb, convRGB2SRGB);
- saveMatToCsv(Path.Combine(saveFolder, GetFilenameWithChannel("File/TccSrgbValues", channel)), tccSrgb);
+ using (var tccSrgb = ConvertColor(tccRgb, convRGB2SRGB)) {
+ saveMatToCsv(Path.Combine(saveFolder, GetFilenameWithChannel("File/TccSrgbValues", channel)), tccSrgb);
- var convSrgb2Xyz = CalcConvertMatrix(tccSrgb, TCC_XYZ, channel);
- saveMatToCsv(Path.Combine(saveFolder, GetFilenameWithChannel("File/ConvSrgb2Xyz", channel)), convSrgb2Xyz);
+ using (var convSrgb2Xyz = CalcConvertMatrix(tccSrgb, TCC_XYZ, channel)) {
+ saveMatToCsv(Path.Combine(saveFolder, GetFilenameWithChannel("File/ConvSrgb2Xyz", channel)), convSrgb2Xyz);
+ }
- // 変換精度検証
- var diff = Math.Sqrt(Cv2.Norm(TCC_SRGB, tccSrgb, NormTypes.L2));
- Debug.WriteLine($"{channel}次元 RGB→SRGB 変換行列の誤差 = {diff:.000}");
+ // 変換精度検証
+ var diff = Math.Sqrt(Cv2.Norm(TCC_SRGB, tccSrgb, NormTypes.L2));
+ Debug.WriteLine($"{channel}次元 RGB→SRGB 変換行列の誤差 = {diff:.000}");
+ }
+ // convRGB2SRGB は辞書に保持するため解放しない(ConvertImage で使用)
_convRGB2SRGB.Add(channel, convRGB2SRGB);
}
}
diff --git a/TIASshot/DBK33UX178.xml b/TIASshot/DBK33UX178.xml
new file mode 100644
index 0000000..6c33e9c
--- /dev/null
+++ b/TIASshot/DBK33UX178.xml
@@ -0,0 +1,141 @@
+
+
+ RGB24 (3072x2048)
+ 30.000030
+ 0
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
diff --git a/TIASshot/DFK33UX178.xml b/TIASshot/DFK33UX178.xml
index 6c33e9c..5806f31 100644
--- a/TIASshot/DFK33UX178.xml
+++ b/TIASshot/DFK33UX178.xml
@@ -1,9 +1,11 @@
-
+
RGB24 (3072x2048)
30.000030
0
+ 0
+
-
@@ -42,10 +44,10 @@
-
+
-
+
@@ -58,6 +60,9 @@
+
+
+
-
@@ -67,7 +72,10 @@
-
+
+
+
+
@@ -88,6 +96,11 @@
+ -
+
+
+
+
-
@@ -123,7 +136,7 @@
- -
+
-
diff --git a/TIASshot/Properties/AssemblyInfo.cs b/TIASshot/Properties/AssemblyInfo.cs
index 308d4a7..2a98256 100644
--- a/TIASshot/Properties/AssemblyInfo.cs
+++ b/TIASshot/Properties/AssemblyInfo.cs
@@ -30,5 +30,5 @@
// ビルド番号
// リビジョン
//
-[assembly: AssemblyVersion("1.6.0.0")]
-[assembly: AssemblyFileVersion("1.6.0.0")]
+[assembly: AssemblyVersion("1.8.0.0")]
+[assembly: AssemblyFileVersion("1.8.0.0")]