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..e3e7154 100644
--- a/TIASshot/Cameras/CameraBase.cs
+++ b/TIASshot/Cameras/CameraBase.cs
@@ -269,18 +269,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/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);
}
}