diff --git a/TIASshot/Cameras/CameraBase.cs b/TIASshot/Cameras/CameraBase.cs index e3e7154..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}枚目保存"); } 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 eadebea..c253291 100644 --- a/TIASshot/ColorCorrection/ColorCorrector.cs +++ b/TIASshot/ColorCorrection/ColorCorrector.cs @@ -198,21 +198,48 @@ /// /// 8bit 画像 internal Mat ConvertImage(Mat src, Mat conv) { + // 1. 型変換: src を CV_64FC3 に変換(in-place) if (src.Type() != MatType.CV_64FC3) { src.ConvertTo(src, MatType.CV_64FC3); } - using (var flatten = src.Reshape(3, src.Height * src.Width)) - using (var extended = ExtendMat(flatten, conv.Rows)) - using (var converted = (extended * conv).ToMat()) - using (var convertedImage = converted.Reshape(3, src.Height)) { - //Clipping to 8bit range - //unsafe { - // convertedImage.ForEachAsVec3d(Clip8bit); - //} - var convImg8 = new Mat(); + // 2. Reshape: メモリ共有を避けるため Clone を使用 + Mat flatten = null; + Mat extended = null; + Mat matrixProduct = null; + Mat convertedImage = null; + Mat convImg8 = null; + + try { + // Reshape は元のメモリを共有するため、Clone で独立コピーを作成 + using (var flattenView = src.Reshape(3, src.Height * src.Width)) { + flatten = flattenView.Clone(); + } + + // 3. 行列拡張 + extended = ExtendMat(flatten, conv.Rows); + + // 4. 行列乗算: MatExpr の一時オブジェクトを避けるため、明示的に ToMat + Clone + using (var product = (extended * conv).ToMat()) { + matrixProduct = product.Clone(); + } + + // 5. Reshape: 再び Clone で安全化 + using (var convertedView = matrixProduct.Reshape(3, src.Height)) { + convertedImage = convertedView.Clone(); + } + + // 6. 8bit 変換 + convImg8 = new Mat(); convertedImage.ConvertTo(convImg8, MatType.CV_8UC3); + return convImg8; + } finally { + // 中間 Mat を確実に破棄(convImg8 は返り値なので破棄しない) + flatten?.Dispose(); + extended?.Dispose(); + matrixProduct?.Dispose(); + convertedImage?.Dispose(); } } @@ -224,7 +251,9 @@ internal Mat ExtendMat(Mat src, int channels) { if (src.Cols * src.Channels() != 3) return src; var dst = new Mat(src.Rows, channels, MatType.CV_64FC1); - Parallel.For(0, src.Rows, row => { + // Parallel.For を通常の for ループに変更 + // Releaseモード最適化 + 低スペックPCでのスレッド競合を回避 + for (int row = 0; row < src.Rows; row++) { var vals = new double[] { src.Cols == 1 ? src.At(row, 0)[2] : src.At(row, 2), // R src.Cols == 1 ? src.At(row, 0)[1] : src.At(row, 1), // G @@ -234,7 +263,7 @@ for (int i = 0; i < channels; i++) { dst.At(row, i) = vals[ExtendChannels[i][0]] * vals[ExtendChannels[i][1]] * vals[ExtendChannels[i][2]]; } - }); + } return dst; } } 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")]