diff --git "a/docs/02_ENV/ENV_02_\343\203\207\343\202\243\343\203\254\343\202\257\343\203\210\343\203\252\346\247\213\346\210\220.md" "b/docs/02_ENV/ENV_02_\343\203\207\343\202\243\343\203\254\343\202\257\343\203\210\343\203\252\346\247\213\346\210\220.md" index 98dfbbb..77d975a 100644 --- "a/docs/02_ENV/ENV_02_\343\203\207\343\202\243\343\203\254\343\202\257\343\203\210\343\203\252\346\247\213\346\210\220.md" +++ "b/docs/02_ENV/ENV_02_\343\203\207\343\202\243\343\203\254\343\202\257\343\203\210\343\203\252\346\247\213\346\210\220.md" @@ -67,6 +67,8 @@ docs/ ├── 01_GUIDE/ ← チームルール・規約(命名,Git,コーディング,コメント) ├── 02_ENV/ ← 開発環境・構成に関するドキュメント(本ディレクトリ) -├── 03_SPEC/ ← 機能仕様・設定パラメータ定義 -└── 04_TECH/ ← クラス設計・アルゴリズム等の技術設計 +├── 03_PLAN/ ← 進行計画・要件定義(リファクタリング計画,バグ修正計画) +├── 04_SPEC/ ← ユーザーから見える仕様(機能仕様,設定ファイル仕様) +├── 05_TECH/ ← クラス設計・アルゴリズム等の技術設計(カメラ制御,色補正処理) +└── 06_TEST/ ← テスト計画・品質保証(テスト仕様) ``` diff --git "a/docs/03_PLAN/PLAN_01_\343\203\252\343\203\225\343\202\241\343\202\257\343\202\277\343\203\252\343\203\263\343\202\260\350\250\210\347\224\273.md" "b/docs/03_PLAN/PLAN_01_\343\203\252\343\203\225\343\202\241\343\202\257\343\202\277\343\203\252\343\203\263\343\202\260\350\250\210\347\224\273.md" new file mode 100644 index 0000000..d8464c1 --- /dev/null +++ "b/docs/03_PLAN/PLAN_01_\343\203\252\343\203\225\343\202\241\343\202\257\343\202\277\343\203\252\343\203\263\343\202\260\350\250\210\347\224\273.md" @@ -0,0 +1,103 @@ +# リファクタリング計画 (Refactoring Plan) + +## 方針 (Policy) + +- 処理の変更は一切行わない.構造の整理のみを目的とする. +- 各フェーズ完了後に必ずテストを実施し,動作が変わっていないことを確認してから次へ進む. +- テストの具体的な手順は `TEST_01_テスト仕様.md` を参照する. + +## 現状の問題点 (Current Issues) + +`CameraBase.cs` に以下の責務が集中している. + +- カメラ制御の抽象定義(Connect/Disconnect/Shot) +- チャート検出(ArUco マーカー検出,ホモグラフィ計算,マスク生成) +- 色補正処理(TCC行列計算,多項式拡張,画像変換) +- ファイル I/O(CSV読み書き,保存フォルダ生成,撮影情報出力) +- サウンド再生 + +## 目標とするディレクトリ構成 (Target Structure) + +```text +TIASshot/ +├── Cameras/ ← カメラ制御(CameraBase, IScam, Lucam) +├── ChartDetection/ ← チャート検出(CameraBaseから分離) +├── ColorCorrection/ ← 色補正・TCC計算(CameraBaseから分離) +├── UI/ ← フォーム(Form1, PreviewMonitor) +├── Config.cs ← 設定ファイル読み込み(変更なし) +├── LightSource.cs ← 光源制御(変更なし) +└── Program.cs ← エントリポイント(変更なし) +``` + +## フェーズ構成 (Phases) + +### フェーズ1: フォルダ整理とファイル移動 + +**作業内容** + +- 以下のフォルダを作成し,既存ファイルを移動する. + - `Cameras/` ← CameraBase.cs, IScam.cs, Lucam.cs + - `UI/` ← Form1.cs, Form1.Designer.cs, Form1.resx, PreviewMonitor.cs, PreviewMonitor.Designer.cs, PreviewMonitor.resx +- 名前空間(namespace)はそのまま `TIASshot` で統一する. +- ファイルの中身は変更しない. + +**テスト** + +- ビルドが成功することを確認する. +- アプリが起動し,カメラが接続されることを確認する(手動). + +### フェーズ2: 色補正処理の分離 + +**作業内容** + +- `ColorCorrection/` フォルダを作成する. +- CameraBase から以下を新クラス `ColorCorrector` に切り出す. + - フィールド: `TCC_SRGB`, `TCC_XYZ`, `ChannelList`, `ExtendChannels`, `UpdateRate` + - メソッド: `ExtendMat`, `CalcConvertMatrix`, `ConvertColor`, `ConvertImage` + - メソッド: `CalcTcc`(チャート RGB 値の受け取りと行列計算・保存を担当) +- CameraBase は `ColorCorrector` を保持し,処理を委譲する. + +**テスト** + +- ビルドが成功することを確認する. +- 回帰テスト: 同一入力画像から算出した変換行列が,リファクタリング前と一致することを確認する. + - 具体的な手順は `TEST_01_テスト仕様.md` の「回帰テスト: 色補正処理」を参照する. + +### フェーズ3: チャート検出処理の分離 + +**作業内容** + +- `ChartDetection/` フォルダを作成する. +- CameraBase から以下を新クラス `ChartDetector` に切り出す. + - フィールド: `ARDict`, `PointsDst40`, `PointsDst41`, `_detectionCount`, `_lastPosition` + - メソッド: `DetectChart` + - 出力: チャートマスクリスト(`List`)を戻り値または引数で渡す +- CameraBase は `ChartDetector` を保持し,処理を委譲する. + +**テスト** + +- ビルドが成功することを確認する. +- 回帰テスト: 保存済みの実画像を入力として,チャートマスクの位置が一致することを確認する. + - 具体的な手順は `TEST_01_テスト仕様.md` の「チャート検出テスト」を参照する. + +### フェーズ4: ファイル I/O・サウンドの分離 + +**作業内容** + +- CameraBase から以下をユーティリティクラスとして切り出す(配置先は `Cameras/` または新フォルダ). + - `LoadMatFromCsv`, `SaveMatToCsv` + - `SetSaveFolder`, `WriteInfo` + - `EventSound` + +**テスト** + +- ビルドが成功することを確認する. +- 撮影からファイル保存までの一連の動作を手動で確認する. +- 保存された CSV・画像の内容がリファクタリング前と一致することを確認する. + +## 共通ルール (Common Rules) + +- 各フェーズは独立したブランチではなく,`refactor/code-cleanup` ブランチ上で順に進める. +- フェーズ完了ごとにコミットする.コミットメッセージは `[clean]` タグを使用する. + - 例: `[clean] 色補正処理を ColorCorrector クラスに分離` +- テストが通らない場合は,次のフェーズに進まない. diff --git "a/docs/03_PLAN/PLAN_02_\343\203\220\343\202\260\344\277\256\346\255\243\350\250\210\347\224\273.md" "b/docs/03_PLAN/PLAN_02_\343\203\220\343\202\260\344\277\256\346\255\243\350\250\210\347\224\273.md" new file mode 100644 index 0000000..2d3d5fb --- /dev/null +++ "b/docs/03_PLAN/PLAN_02_\343\203\220\343\202\260\344\277\256\346\255\243\350\250\210\347\224\273.md" @@ -0,0 +1,115 @@ +# バグ修正計画 (Bug Fix Plan) + +## 前提 (Prerequisites) + +- `PLAN_01_リファクタリング計画.md` の全フェーズが完了し,動作がリファクタリング前と同一であることを確認してから着手する. +- 修正は1件ずつ独立して行い,都度テストを実施する. +- コミットメッセージは `[fix]` タグを使用する. + - 例: `[fix] _shots へのスレッドセーフなアクセスに修正` + +## 症状 (Symptom) + +- 連続撮影で4枚以上取得するとアプリがたまにクラッシュする. +- 再現性は不安定(毎回ではなく「たまに」落ちる). + +## 原因と修正方針 (Causes and Fix Policy) + +### 優先度1: `_shots` へのスレッドセーフでないアクセス(根本原因) + +**原因** + +`_shots`(`List`)は Shot スレッドと SaveThread から同時にアクセスされる. +`List` はスレッドセーフではないため,以下の2点で競合が発生する. + +- リサイズ競合 + - `List` の内部配列は容量が足りなくなると自動で拡張される(デフォルト容量 4 → 5枚目の Add 時に 8 に拡張). + - 拡張中に SaveThread が `_shots[saveCount]` を読むとメモリ破壊が起きる. + - 「4枚以上でたまに落ちる」という症状に一致する. +- TOCTOU(確認と使用の間の競合) + - SaveThread の `_shots.Count` チェックと `_shots[saveCount]` アクセスの間に Shot スレッドがリサイズを起こすと `IndexOutOfRangeException` が発生する. + +**修正方針** + +- `_shots` を `List` から `System.Collections.Concurrent.ConcurrentQueue` またはロック付きアクセスに変更する. +- SaveThread の待機ロジックをスレッドセーフな方法(`SemaphoreSlim` 等)に変更する. + +**テスト** + +- 連続撮影を10枚以上で複数回繰り返し,クラッシュしないことを確認する. + +### 優先度2: `ConvertImage` 内の Mat 未 Dispose によるメモリ蓄積 + +**原因** + +`ConvertImage` 内で生成される中間 Mat(`extended`,`converted`,`convertedImage`)が Dispose されないまま残る. +1回の呼び出しで数百 MB〜1 GB 超が蓄積し,画像枚数×チャンネル数分だけ積み重なる. +(例: IScam 1840×2440,17次元,4枚連続撮影 → 数十 GB 規模) + +**修正方針** + +- `extended`,`converted`,`convertedImage` を `using` ブロックで管理して確実に Dispose する. + +**テスト** + +- タスクマネージャでメモリ使用量を監視しながら連続撮影を繰り返し,撮影ごとにメモリが増加し続けないことを確認する. + +### 優先度3: `Parallel.For` 内での未捕捉例外 + +**原因** + +`ExtendMat` の `Parallel.For` 内で OOM 等の例外が発生した場合,`AggregateException` としてラップされて SaveThread に伝播する. +SaveThread で捕捉されなければアプリがクラッシュする. +※ 優先度2の修正でメモリ蓄積が解消されれば,発生頻度は大幅に下がる. + +**修正方針** + +- SaveThread に例外ハンドラを追加し,クラッシュではなくエラーメッセージを表示して処理を中断するようにする. +- `ExtendMat` の `Parallel.For` に try-catch を追加する. + +**テスト** + +- 例外発生時にアプリが落ちずエラーが表示されることを確認する. + +### 優先度4: `ConvertImage` による `_shots[i]` の in-place 書き換え + +**原因** + +`ConvertImage` 内の `src.ConvertTo(src, CV_64FC3)` が `_shots[i]` 自体を CV_8UC3 から CV_64FC3 に書き換える副作用がある. +特に IScam の `new Mat(imgt_full, _roi)`(サブマトリクス)に対して in-place の型変換を行うと不正な動作を引き起こす可能性がある. +また複数チャンネルを処理する際,2回目以降は変換済みのデータに対して処理をかけることになり,変換結果が誤る. + +**修正方針** + +- `ConvertImage` の冒頭で `src` をコピーし,コピー側を変換するようにする(`src` を直接書き換えない). + +**テスト** + +- 複数チャンネル(例: `4,10,17`)で撮影し,各チャンネルの sRGB 変換画像が正しく出力されることを確認する. + +### 優先度5: Mat 未 Dispose によるメモリリーク + +**原因 A: `_shots.Clear()` で Mat が解放されない** + +- 撮影ごとに `_shots.Clear()` が呼ばれるが,格納された Mat は Dispose されない. + +**原因 B: `_chartMasks.Clear()` で Mat が解放されない** + +- チャート再検出時に `_chartMasks.Clear()` が呼ばれるが,同様に Dispose されない. + +**修正方針** + +- Clear 前に各 Mat を Dispose するヘルパーを追加する. + +**テスト** + +- タスクマネージャでメモリ使用量を監視し,撮影・キャリブを繰り返してもメモリが増加し続けないことを確認する. + +## 修正順序まとめ (Summary) + +| 順位 | 原因 | クラッシュとの関連 | +| --- | --- | --- | +| 1 | `_shots` のスレッドセーフでないアクセス | 直接原因(「たまに落ちる」に一致) | +| 2 | `ConvertImage` 内の Mat 未 Dispose | 直接原因(OOM によるクラッシュ) | +| 3 | `Parallel.For` 内の未捕捉例外 | 間接原因(2の修正で頻度減) | +| 4 | `ConvertImage` の in-place 書き換え | 誤動作(変換結果の誤り) | +| 5 | `_shots` / `_chartMasks` の Mat 未 Dispose | メモリリーク(蓄積) | diff --git "a/docs/03_SPEC/SPEC_01_\346\251\237\350\203\275\344\273\225\346\247\230.md" "b/docs/03_SPEC/SPEC_01_\346\251\237\350\203\275\344\273\225\346\247\230.md" deleted file mode 100644 index 7feab10..0000000 --- "a/docs/03_SPEC/SPEC_01_\346\251\237\350\203\275\344\273\225\346\247\230.md" +++ /dev/null @@ -1,96 +0,0 @@ -# 機能仕様 (Feature Specification) - -## 対応カメラ (Supported Cameras) - -起動時に Lucam → ImagingSource の順で接続を試みる. - -**ImagingSource カメラ(新カメラ)** - -- 対応機種: DFK33UX178,DFK23UX249 - - ※ README には DBK33UX178 と記載されていたが,ソースコード上は DFK33UX178 として扱われている.実機の型番は要確認. -- SDK: IC Imaging Control (TIS.Imaging) -- 設定ファイル: `[デバイス名].xml`(例: `DFK33UX178.xml`) - -**Lumenera カメラ(旧カメラ)** - -- 対応機種: Lw110 -- SDK: Lumenera SDK(`lumenera.api.dll`) - -## プレビュー (Preview) - -- 起動後,接続したカメラのライブ映像をメインウィンドウに表示する. -- サブモニタへのプレビュー表示にも対応する(PreviewMonitor). -- ImagingSource カメラはプレビュー時に ROI クリップを適用する. - -## 自動キャリブレーション (Auto Calibration) - -プレビュー中に舌診チャートを検出すると自動でキャリブレーションを開始する. - -### チャート検出 - -- ArUco マーカー(ID 40, 41)を使用してチャートを自動検出する. -- チャートが一定フレーム数静止したことを確認してからキャリブを開始する. - -### ホワイトバランス調整 - -- チャートの白色票(24色票の13番)を基準にカメラのゲインを自動調整する. -- 目標値は `config.xml` の `Calib/Reference` で指定する. - -### TCC(色変換行列)計算 - -- 24パッチの平均 RGB 値を計測し,最小二乗法(SVD)で変換行列を算出する. -- 変換次元数(多項式の次数)は `Calib/ConversionChannels` で指定する. - - 4: 1次式(R,G,B,1) - - 10: 2次式(+RG,RB,GB,R²,G²,B²) - - 17: 3次式(+R²B,R²G,G²R,G²B,B²R,B²G,RGB) -- 複数の次元数をカンマ区切りで同時に指定可能(例: `4,10,17`). -- 算出した変換行列は CSV ファイルに保存される. - -## 撮影 (Shooting) - -キャリブレーション完了後に撮影ボタンが有効になる. - -**1枚撮影** - -- 撮影した画像を1枚保存する. - -**連続撮影** - -- 指定した枚数・間隔で連続撮影する. -- 枚数・間隔は UI で変更可能(デフォルト値は `config.xml` で設定). - -**保存される画像** - -- 元画像(RGB): `Shot{NO}.png` -- sRGB 変換画像: `sRGB{CN}_{NO}.jpg`(指定した次元数ぶん生成される) - -## データ保存 (Data Saving) - -撮影・キャリブレーション結果は保存フォルダ以下に日時フォルダで自動保存される. - -**保存先フォルダ構成** - -- `[SaveFolder]/[日付]/[時刻]-[データ名]/` - -**キャリブレーション保存ファイル** - -- `TCC.png`: キャリブ時の画像 -- `TCC_ROIs.jpg`: チャートの ROI 確認画像 -- `TCC_RGB.csv`: チャート24色票の RGB 値 -- `TCC_sRGB{CN}.csv`: チャート24色票の sRGB 変換値 -- `Conv_RGB-sRGB{CN}.csv`: RGB→sRGB 変換行列 -- `Conv_RGB-XYZ{CN}.csv`: RGB→XYZ 変換行列 -- `Conv_sRGB-XYZ{CN}.csv`: sRGB→XYZ 変換行列 - -**撮影保存ファイル** - -- `Info.csv`: 撮影情報(カメラ型番・SN,露光時間,ゲイン等) -- `Shot{NO}.png`: 元画像(RGB) -- `sRGB{CN}_{NO}.jpg`: sRGB 変換画像 - -## 光源制御 (Light Source Control) - -- シリアル通信(USB-シリアル変換)で光源の ON/OFF を制御する. -- 対応デバイス: Silicon Labs CP210x(`config.xml` で変更可能). -- キャリブレーション中は光源の切り替えが無効になる. -- 光源デバイスが見つからない場合,光源ボタンが無効化される. diff --git "a/docs/03_SPEC/SPEC_02_\350\250\255\345\256\232\343\203\225\343\202\241\343\202\244\343\203\253\344\273\225\346\247\230.md" "b/docs/03_SPEC/SPEC_02_\350\250\255\345\256\232\343\203\225\343\202\241\343\202\244\343\203\253\344\273\225\346\247\230.md" deleted file mode 100644 index 24a3266..0000000 --- "a/docs/03_SPEC/SPEC_02_\350\250\255\345\256\232\343\203\225\343\202\241\343\202\244\343\203\253\344\273\225\346\247\230.md" +++ /dev/null @@ -1,129 +0,0 @@ -# 設定ファイル仕様 (Configuration File Specification) - -実行ファイルと同じディレクトリに `config.xml` を配置する. -XML のルート要素は `` とする. - -※ ファイル名・保存先のテンプレート変数: `{NO}` は4桁連番,`{CN}` は2桁の次元数に展開される. - -## Lucam カメラ設定 (Lucam Camera) - -Lumenera Lw110 の初期パラメータ. - -**``** - -- ``: 露光時間 (ms) - - 例: `60.0` -- ``: マスターゲイン - - 例: `1.5` -- ``: ブルーゲイン - - 例: `2.62` -- ``: グリーンゲイン - - 例: `1.72` -- ``: レッドゲイン - - 例: `1.70` - -## ImagingSource カメラ設定 (ImagingSource Camera) - -各カメラの ROI(関心領域)設定.カメラ名はデバイス名と一致させる. - -**`` / ``** - -- `` - - ``: ROI の左端座標 (px) - - ``: ROI の上端座標 (px) - - ``: ROI の幅 (px) - - ``: ROI の高さ (px) - -※ ImagingSource カメラは画像を転置してから ROI を切り出す.X・Y・W・H は転置後の座標系で指定する. - -## キャリブレーション設定 (Calibration) - -**``** - -- ``: チャート固定判定の閾値 (px) - - ARマーカーの移動量がこの値未満のフレームを「静止」とみなす. - - 例: `4.0` -- ``: チャート固定判定に必要な連続静止フレーム数 - - 例: `30` -- ``: 白色票(24色票の13番)の目標 RGB 値 - - ``: ブルー目標値 (0-255) - - ``: グリーン目標値 (0-255) - - ``: レッド目標値 (0-255) - - 例: `226`(各チャンネル共通) -- ``: ホワイトバランスゲインの更新率(0.0-1.0) - - 1.0 で即時反映,値を下げると緩やかに収束する. - - 例: `0.8` -- ``: キャリブレーションに使用するフレーム数 - - 例: `100` -- ``: ゲインを更新するフレーム間隔 - - 例: `5`(5フレームごとに更新) -- ``: 変換行列の次元数 - - 4 (1次式),10 (2次式),17 (3次式) から選択する. - - カンマ区切りで複数指定可能(例: `4,10,17`). - - 詳細は `SPEC_01_機能仕様.md` の「TCC(色変換行列)計算」を参照. - -## 撮影設定 (Shot) - -**``** - -- ``: プレビューモニタの表示幅 (px) - - 例: `1280` -- ``: 連続撮影の枚数(UI の初期値) - - 例: `10` -- ``: 連続撮影の間隔 (ms)(UI の初期値) - - 例: `500` -- ``: データ名の初期値(UI のテキストボックスに表示) - - 例: `無記名` - -## ファイル設定 (File) - -**``** - -- ``: データの保存先フォルダ(絶対パス) - - 例: `C:\TIAS_Data` -- ``: sRGB 変換参照値 CSV のパス - - 例: `tcc_srgb.csv` -- ``: XYZ 変換参照値 CSV のパス - - 例: `tcc_xyz.csv` -- ``: キャリブ時の画像ファイル名 - - 例: `TCC.png` -- ``: チャート ROI 確認画像ファイル名 - - 例: `TCC_ROIs.jpg` -- ``: 撮影画像ファイル名(`{NO}` で連番) - - 例: `Shot{NO}.png` -- ``: sRGB 変換画像ファイル名(`{CN}` で次元数,`{NO}` で連番) - - 例: `sRGB{CN}_{NO}.jpg` -- ``: 撮影情報 CSV のファイル名 - - 例: `Info.csv` -- ``: チャート RGB 値 CSV のファイル名 - - 例: `TCC_RGB.csv` -- ``: チャート sRGB 値 CSV のファイル名(`{CN}` で次元数) - - 例: `TCC_sRGB{CN}.csv` -- ``: RGB→sRGB 変換行列 CSV のファイル名 - - 例: `Conv_RGB-sRGB{CN}.csv` -- ``: RGB→XYZ 変換行列 CSV のファイル名 - - 例: `Conv_RGB-XYZ{CN}.csv` -- ``: sRGB→XYZ 変換行列 CSV のファイル名 - - 例: `Conv_sRGB-XYZ{CN}.csv` -- ``: RGB→XYZ 変換行列(旧形式)CSV のファイル名 - - 例: `Conv_RGB-XYZold.csv` - -## 光源設定 (Light Source) - -**``** - -- ``: 光源デバイスのデバイス名(部分一致で検索) - - 例: `Silicon Labs CP210x` -- ``: シリアル通信のボーレート (bps) - - 例: `38400` - -## サウンド設定 (Sound) - -**``** - -- ``: キャリブレーション完了時の効果音ファイル名 - - 例: `CalibDone.wav` -- ``: 1枚撮影完了時の効果音ファイル名 - - 例: `OneShot.wav` -- ``: 連続撮影完了時の効果音ファイル名 - - 例: `MultiShot.wav` diff --git "a/docs/04_SPEC/SPEC_01_\346\251\237\350\203\275\344\273\225\346\247\230.md" "b/docs/04_SPEC/SPEC_01_\346\251\237\350\203\275\344\273\225\346\247\230.md" new file mode 100644 index 0000000..7feab10 --- /dev/null +++ "b/docs/04_SPEC/SPEC_01_\346\251\237\350\203\275\344\273\225\346\247\230.md" @@ -0,0 +1,96 @@ +# 機能仕様 (Feature Specification) + +## 対応カメラ (Supported Cameras) + +起動時に Lucam → ImagingSource の順で接続を試みる. + +**ImagingSource カメラ(新カメラ)** + +- 対応機種: DFK33UX178,DFK23UX249 + - ※ README には DBK33UX178 と記載されていたが,ソースコード上は DFK33UX178 として扱われている.実機の型番は要確認. +- SDK: IC Imaging Control (TIS.Imaging) +- 設定ファイル: `[デバイス名].xml`(例: `DFK33UX178.xml`) + +**Lumenera カメラ(旧カメラ)** + +- 対応機種: Lw110 +- SDK: Lumenera SDK(`lumenera.api.dll`) + +## プレビュー (Preview) + +- 起動後,接続したカメラのライブ映像をメインウィンドウに表示する. +- サブモニタへのプレビュー表示にも対応する(PreviewMonitor). +- ImagingSource カメラはプレビュー時に ROI クリップを適用する. + +## 自動キャリブレーション (Auto Calibration) + +プレビュー中に舌診チャートを検出すると自動でキャリブレーションを開始する. + +### チャート検出 + +- ArUco マーカー(ID 40, 41)を使用してチャートを自動検出する. +- チャートが一定フレーム数静止したことを確認してからキャリブを開始する. + +### ホワイトバランス調整 + +- チャートの白色票(24色票の13番)を基準にカメラのゲインを自動調整する. +- 目標値は `config.xml` の `Calib/Reference` で指定する. + +### TCC(色変換行列)計算 + +- 24パッチの平均 RGB 値を計測し,最小二乗法(SVD)で変換行列を算出する. +- 変換次元数(多項式の次数)は `Calib/ConversionChannels` で指定する. + - 4: 1次式(R,G,B,1) + - 10: 2次式(+RG,RB,GB,R²,G²,B²) + - 17: 3次式(+R²B,R²G,G²R,G²B,B²R,B²G,RGB) +- 複数の次元数をカンマ区切りで同時に指定可能(例: `4,10,17`). +- 算出した変換行列は CSV ファイルに保存される. + +## 撮影 (Shooting) + +キャリブレーション完了後に撮影ボタンが有効になる. + +**1枚撮影** + +- 撮影した画像を1枚保存する. + +**連続撮影** + +- 指定した枚数・間隔で連続撮影する. +- 枚数・間隔は UI で変更可能(デフォルト値は `config.xml` で設定). + +**保存される画像** + +- 元画像(RGB): `Shot{NO}.png` +- sRGB 変換画像: `sRGB{CN}_{NO}.jpg`(指定した次元数ぶん生成される) + +## データ保存 (Data Saving) + +撮影・キャリブレーション結果は保存フォルダ以下に日時フォルダで自動保存される. + +**保存先フォルダ構成** + +- `[SaveFolder]/[日付]/[時刻]-[データ名]/` + +**キャリブレーション保存ファイル** + +- `TCC.png`: キャリブ時の画像 +- `TCC_ROIs.jpg`: チャートの ROI 確認画像 +- `TCC_RGB.csv`: チャート24色票の RGB 値 +- `TCC_sRGB{CN}.csv`: チャート24色票の sRGB 変換値 +- `Conv_RGB-sRGB{CN}.csv`: RGB→sRGB 変換行列 +- `Conv_RGB-XYZ{CN}.csv`: RGB→XYZ 変換行列 +- `Conv_sRGB-XYZ{CN}.csv`: sRGB→XYZ 変換行列 + +**撮影保存ファイル** + +- `Info.csv`: 撮影情報(カメラ型番・SN,露光時間,ゲイン等) +- `Shot{NO}.png`: 元画像(RGB) +- `sRGB{CN}_{NO}.jpg`: sRGB 変換画像 + +## 光源制御 (Light Source Control) + +- シリアル通信(USB-シリアル変換)で光源の ON/OFF を制御する. +- 対応デバイス: Silicon Labs CP210x(`config.xml` で変更可能). +- キャリブレーション中は光源の切り替えが無効になる. +- 光源デバイスが見つからない場合,光源ボタンが無効化される. diff --git "a/docs/04_SPEC/SPEC_02_\350\250\255\345\256\232\343\203\225\343\202\241\343\202\244\343\203\253\344\273\225\346\247\230.md" "b/docs/04_SPEC/SPEC_02_\350\250\255\345\256\232\343\203\225\343\202\241\343\202\244\343\203\253\344\273\225\346\247\230.md" new file mode 100644 index 0000000..24a3266 --- /dev/null +++ "b/docs/04_SPEC/SPEC_02_\350\250\255\345\256\232\343\203\225\343\202\241\343\202\244\343\203\253\344\273\225\346\247\230.md" @@ -0,0 +1,129 @@ +# 設定ファイル仕様 (Configuration File Specification) + +実行ファイルと同じディレクトリに `config.xml` を配置する. +XML のルート要素は `` とする. + +※ ファイル名・保存先のテンプレート変数: `{NO}` は4桁連番,`{CN}` は2桁の次元数に展開される. + +## Lucam カメラ設定 (Lucam Camera) + +Lumenera Lw110 の初期パラメータ. + +**``** + +- ``: 露光時間 (ms) + - 例: `60.0` +- ``: マスターゲイン + - 例: `1.5` +- ``: ブルーゲイン + - 例: `2.62` +- ``: グリーンゲイン + - 例: `1.72` +- ``: レッドゲイン + - 例: `1.70` + +## ImagingSource カメラ設定 (ImagingSource Camera) + +各カメラの ROI(関心領域)設定.カメラ名はデバイス名と一致させる. + +**`` / ``** + +- `` + - ``: ROI の左端座標 (px) + - ``: ROI の上端座標 (px) + - ``: ROI の幅 (px) + - ``: ROI の高さ (px) + +※ ImagingSource カメラは画像を転置してから ROI を切り出す.X・Y・W・H は転置後の座標系で指定する. + +## キャリブレーション設定 (Calibration) + +**``** + +- ``: チャート固定判定の閾値 (px) + - ARマーカーの移動量がこの値未満のフレームを「静止」とみなす. + - 例: `4.0` +- ``: チャート固定判定に必要な連続静止フレーム数 + - 例: `30` +- ``: 白色票(24色票の13番)の目標 RGB 値 + - ``: ブルー目標値 (0-255) + - ``: グリーン目標値 (0-255) + - ``: レッド目標値 (0-255) + - 例: `226`(各チャンネル共通) +- ``: ホワイトバランスゲインの更新率(0.0-1.0) + - 1.0 で即時反映,値を下げると緩やかに収束する. + - 例: `0.8` +- ``: キャリブレーションに使用するフレーム数 + - 例: `100` +- ``: ゲインを更新するフレーム間隔 + - 例: `5`(5フレームごとに更新) +- ``: 変換行列の次元数 + - 4 (1次式),10 (2次式),17 (3次式) から選択する. + - カンマ区切りで複数指定可能(例: `4,10,17`). + - 詳細は `SPEC_01_機能仕様.md` の「TCC(色変換行列)計算」を参照. + +## 撮影設定 (Shot) + +**``** + +- ``: プレビューモニタの表示幅 (px) + - 例: `1280` +- ``: 連続撮影の枚数(UI の初期値) + - 例: `10` +- ``: 連続撮影の間隔 (ms)(UI の初期値) + - 例: `500` +- ``: データ名の初期値(UI のテキストボックスに表示) + - 例: `無記名` + +## ファイル設定 (File) + +**``** + +- ``: データの保存先フォルダ(絶対パス) + - 例: `C:\TIAS_Data` +- ``: sRGB 変換参照値 CSV のパス + - 例: `tcc_srgb.csv` +- ``: XYZ 変換参照値 CSV のパス + - 例: `tcc_xyz.csv` +- ``: キャリブ時の画像ファイル名 + - 例: `TCC.png` +- ``: チャート ROI 確認画像ファイル名 + - 例: `TCC_ROIs.jpg` +- ``: 撮影画像ファイル名(`{NO}` で連番) + - 例: `Shot{NO}.png` +- ``: sRGB 変換画像ファイル名(`{CN}` で次元数,`{NO}` で連番) + - 例: `sRGB{CN}_{NO}.jpg` +- ``: 撮影情報 CSV のファイル名 + - 例: `Info.csv` +- ``: チャート RGB 値 CSV のファイル名 + - 例: `TCC_RGB.csv` +- ``: チャート sRGB 値 CSV のファイル名(`{CN}` で次元数) + - 例: `TCC_sRGB{CN}.csv` +- ``: RGB→sRGB 変換行列 CSV のファイル名 + - 例: `Conv_RGB-sRGB{CN}.csv` +- ``: RGB→XYZ 変換行列 CSV のファイル名 + - 例: `Conv_RGB-XYZ{CN}.csv` +- ``: sRGB→XYZ 変換行列 CSV のファイル名 + - 例: `Conv_sRGB-XYZ{CN}.csv` +- ``: RGB→XYZ 変換行列(旧形式)CSV のファイル名 + - 例: `Conv_RGB-XYZold.csv` + +## 光源設定 (Light Source) + +**``** + +- ``: 光源デバイスのデバイス名(部分一致で検索) + - 例: `Silicon Labs CP210x` +- ``: シリアル通信のボーレート (bps) + - 例: `38400` + +## サウンド設定 (Sound) + +**``** + +- ``: キャリブレーション完了時の効果音ファイル名 + - 例: `CalibDone.wav` +- ``: 1枚撮影完了時の効果音ファイル名 + - 例: `OneShot.wav` +- ``: 連続撮影完了時の効果音ファイル名 + - 例: `MultiShot.wav` diff --git "a/docs/04_TECH/TECH_01_\343\202\257\343\203\251\343\202\271\350\250\255\350\250\210.md" "b/docs/04_TECH/TECH_01_\343\202\257\343\203\251\343\202\271\350\250\255\350\250\210.md" deleted file mode 100644 index b861ef5..0000000 --- "a/docs/04_TECH/TECH_01_\343\202\257\343\203\251\343\202\271\350\250\255\350\250\210.md" +++ /dev/null @@ -1,87 +0,0 @@ -# クラス設計 (Class Design) - -## クラス一覧 (Class Overview) - -**CameraBase(抽象基底クラス)** - -- ファイル: `CameraBase.cs` -- カメラに共通する処理をまとめた抽象クラス. -- IScam・Lucam の両方が継承する. - -**IScam** - -- ファイル: `IScam.cs` -- ImagingSource カメラ(DFK33UX178,DFK23UX249)の実装. -- IC Imaging Control SDK(`TIS.Imaging`)を使用する. - -**Lucam** - -- ファイル: `Lucam.cs` -- Lumenera カメラ(Lw110)の実装. -- Lumenera SDK(`lumenera.api.dll`)を使用する. - -**Form1** - -- ファイル: `Form1.cs` -- メインウィンドウ.UI イベントの受け取りとカメラ・光源の操作を担う. - -**PreviewMonitor** - -- ファイル: `PreviewMonitor.cs` -- サブモニタへのプレビュー表示フォーム. - -**Config(静的クラス)** - -- ファイル: `Config.cs` -- `config.xml` の読み込みユーティリティ. -- XPath を使って文字列・整数・浮動小数点数の値を取得する. - -**LightSource** - -- ファイル: `LightSource.cs` -- シリアル通信(USB-シリアル変換)による光源の ON/OFF 制御. - -## 継承関係 (Inheritance) - -```text -CameraBase(抽象) -├── IScam ← ImagingSource カメラ -└── Lucam ← Lumenera カメラ -``` - -## CameraBase の主なメンバ (CameraBase Members) - -### 抽象メソッド(派生クラスで実装する) - -- `Connect() : bool`: カメラに接続する -- `Disconnect()`: カメラを切断する -- `Shot(numImages, interval)`: 撮影処理 - -### 公開メソッド - -- `ShotOne()`: 1枚撮影 -- `ShotMulti()`: 連続撮影 - -### 保護メソッド(派生クラスから呼ぶ) - -- `BootCheck() : bool`: 起動時の前提条件チェック(設定・TCC参照ファイル) -- `DetectChart(img)`: ArUco マーカーによるチャート検出 -- `CalcTcc(img)`: TCC 変換行列の計算・保存 -- `ConvertImage(src, conv) : Mat`: 画像の色変換 -- `SaveThread(numImages)`: 画像保存スレッド処理 -- `GetRatio(value, target) : float`: ゲイン更新比率の計算 - -### 主なフィールド - -- `_calibrating`: キャリブレーション残りフレーム数(0 で完了) -- `_calibrated`: キャリブレーション完了フラグ -- `_chartMasks`: 24色票の ROI マスクリスト -- `_convRGB2SRGB`: 次元数をキーとした RGB→sRGB 変換行列の辞書 -- `_roi`: ROI 矩形(IScam のみ使用) -- `_shots`: 撮影済み画像バッファ - -## Form1 とカメラの関係 (Form1 and Camera) - -- Form1 は `CameraBase` 型の `_camera` フィールドを持つ. -- 起動時に Lucam → IScam の順で接続を試みる(詳細は `TECH_02` 参照). -- UI スレッドとカメラスレッドをまたぐメソッド(`ShowMessage`,`EnableShots` 等)は `InvokeRequired` を確認してスレッドセーフに呼び出す. diff --git "a/docs/04_TECH/TECH_02_\343\202\253\343\203\241\343\203\251\345\210\266\345\276\241\344\273\225\346\247\230.md" "b/docs/04_TECH/TECH_02_\343\202\253\343\203\241\343\203\251\345\210\266\345\276\241\344\273\225\346\247\230.md" deleted file mode 100644 index adec6b7..0000000 --- "a/docs/04_TECH/TECH_02_\343\202\253\343\203\241\343\203\251\345\210\266\345\276\241\344\273\225\346\247\230.md" +++ /dev/null @@ -1,88 +0,0 @@ -# カメラ制御仕様 (Camera Control Specification) - -## 起動時のカメラ接続フロー (Startup Connection Flow) - -Form1 ロード時に以下の順で接続を試みる. - -1. `Lucam.Connect()` を実行する - - 成功: Lucam をアクティブカメラとして使用する - - 失敗: 次へ - -2. ImagingSource デバイス一覧を走査する - - 接続されているデバイス名を順に `IScam.Connect()` で試行する - - 最初に成功したカメラをアクティブカメラとして使用する - -3. どのカメラも接続できない場合 - - エラーダイアログを表示してアプリを終了する - -## IScam(ImagingSource カメラ)の制御 (IScam Control) - -### 接続 - -- `[デバイス名].xml` からカメラ設定をロードする. -- `config.xml` の `<カメラ名>/ROI` から ROI 設定を読み込む. -- `FrameQueueSink` をシンクに設定してライブ開始する. - -### プレビュー(フレーム取得コールバック) - -- `FrameQueueSink` の `Retrieve` コールバックでフレームを受け取る. -- 受け取った画像を転置(`.T()`)し,ROI を切り出して表示する. -- キャリブレーション中は白色票の測定・ゲイン更新も行う(後述). - -### ROI クリップ - -- ImagingSource カメラは横向きに画像を出力するため,転置して縦向きに変換する. -- 転置後の画像座標系で ROI(X,Y,W,H)を切り出す. - -### 撮影 - -- シンクを `FrameQueueSink` → `FrameSnapSink` に切り替える. -- `SnapSingle()` で1フレームずつ取得して `_shots` に蓄積する. -- 撮影完了後,シンクを `FrameQueueSink` に戻す. -- 最初の1フレームは捨てる(カメラの安定待ち). - -## Lucam(Lumenera カメラ)の制御 (Lucam Control) - -### 接続 - -- `LucamNumCameras()` で接続台数を確認する(0台または2台以上はエラー). -- カメラ ID から機種を特定する(`0x49f` → Lw110). -- `LucamRgbPreviewCallback` コールバックを登録する. -- `config.xml` の `` からカメラパラメータを設定して,プレビューを開始する. - -### プレビュー(フレーム取得コールバック) - -- `LucamRgbPreviewCallback` でフレームを受け取る. -- 受け取った画像を転置(`.T()`)して表示する(ROI クリップなし). -- キャリブレーション中は白色票の測定・ゲイン更新も行う(後述). - -### 撮影 - -- `LucamEnableFastFrames()` でスナップ撮影モードに切り替える. -- `LucamTakeFastFrame()` でRAW画像を取得し,`LucamConvertFrameToRgb24()` でRGB変換する. -- 撮影完了後,`LucamDisableFastFrames()` で通常モードに戻す. - -## ホワイトバランス自動調整 (White Balance Calibration) - -チャート検出後(`_calibrating` が `Calib/Frames` にセットされた後),各フレームで以下の処理を行う. - -1. 白色票(`_chartMasks[12]`,24色票の13番)の平均 RGB 値を測定する - -2. `Calib/UpdateInterval` フレームごとにゲインを更新する - - 更新比率: `ratio = (target / current - 1) * UpdateRate + 1` - - IScam: `VCDRangeProperty.Value` を直接更新する - - Lucam: `_snap.Gain*` を更新し `SetCameraParam()` で反映する - -3. `_calibrating` をデクリメントし,0 になったら `CalcTcc()` を呼び出す - -## データ保存フォルダの生成 (Save Folder) - -`SetSaveFolder(note)` が保存先フォルダを以下の書式で生成する. - -```text -[SaveFolder]/[yyyy-MM-dd]/[HH_mm_ss]-[note]/ -``` - -- `SaveFolder` は `config.xml` の `` で指定する. -- `note` は「データ名」(撮影時)または「校正」(キャリブ時). -- フォルダが存在しない場合は自動作成する. diff --git "a/docs/04_TECH/TECH_03_\350\211\262\350\243\234\346\255\243\345\207\246\347\220\206\344\273\225\346\247\230.md" "b/docs/04_TECH/TECH_03_\350\211\262\350\243\234\346\255\243\345\207\246\347\220\206\344\273\225\346\247\230.md" deleted file mode 100644 index c320182..0000000 --- "a/docs/04_TECH/TECH_03_\350\211\262\350\243\234\346\255\243\345\207\246\347\220\206\344\273\225\346\247\230.md" +++ /dev/null @@ -1,85 +0,0 @@ -# 色補正処理仕様 (Color Correction Specification) - -## 処理の全体フロー (Overall Flow) - -```text -1. ArUco マーカー検出(DetectChart) - ↓ -2. チャート固定判定 - ↓ -3. ホワイトバランス自動調整(ゲイン更新) - ↓ -4. TCC 変換行列の計算(CalcTcc) - ↓ -5. 変換行列の保存・撮影の有効化 -``` - -## チャート検出(DetectChart) (Chart Detection) - -### ArUco マーカー検出 - -- 辞書: Dict4X4_50(4×4 ビット,50種) -- 使用マーカー ID: 40(チャート上端),41(チャート下端) -- 両方のマーカーが検出できない場合は処理をスキップする. -- ID40 の Y 座標が ID41 より大きい場合(上下逆)は警告を表示する. - -### チャート固定判定 - -- ID40 の検出座標と前フレームの座標の距離を計算する. -- 距離 < `Calib/ChartSetCriteria` が `Calib/ChartSetCount` フレーム連続したら固定と判断する. -- 動きを検出したらカウントをリセットする. - -### ホモグラフィ計算とチャートマスク作成 - -- 2つのマーカーの角点(計8点)からホモグラフィ行列を計算する. -- ホモグラフィで画像を 1545×810 px に正面化する. -- 正面化画像上で 24 色票の ROI を定義し(6列×4行,各 80×80 px),元の画像座標系に逆変換してマスクを生成する. -- 生成したマスクを `_chartMasks` リスト(インデックス 0〜23)に格納する. - -## TCC 変換行列の計算(CalcTcc) (TCC Matrix Calculation) - -### チャート RGB 値の取得 - -- `_chartMasks` を使って 24 パッチの平均 RGB 値を測定する(24×3 行列). - -### 多項式拡張(ExtendMat) - -3チャンネル(R,G,B)を指定の次元数に拡張する. - -**次元数と項の対応** - -| 次元数 | 追加される項 | -| --- | --- | -| 4 (1次式) | 1, R, G, B | -| 10 (2次式) | + RG, RB, GB, R², G², B² | -| 17 (3次式) | + R²B, R²G, G²R, G²B, B²R, B²G, RGB | - -- 各項は `ExtendChannels` テーブルで定義した3チャンネルの積として計算する. - -### 変換行列の算出(CalcConvertMatrix) - -- 拡張行列(24×N)と目標行列(24×3)から最小二乗法(SVD分解)で変換行列(N×3)を算出する. -- `Cv2.Solve(..., DecompTypes.SVD)` を使用する. - -### 算出する変換行列の種類 - -- RGB→sRGB: カメラ RGB から sRGB への変換行列 -- RGB→XYZ: カメラ RGB から XYZ 色空間への変換行列 -- sRGB→XYZ: sRGB から XYZ 色空間への変換行列 -- 各行列は指定した次元数ぶん計算・保存される. - -### TCC参照値ファイル - -- `tcc_srgb.csv`: 24色票の標準 sRGB 値(24×3 行列) -- `tcc_xyz.csv`: 24色票の標準 XYZ 値(24×3 行列) -- アプリ起動時に読み込み,`CameraBase` に保持する. - -## 画像の色変換(ConvertImage) (Image Color Conversion) - -1. 入力画像を `CV_64FC3`(64bit浮動小数点3チャンネル)に変換する -2. 画像を1次元(ピクセル数×3)に変形(Reshape)する -3. 多項式拡張(ExtendMat)でピクセル数×N 行列にする -4. 変換行列(N×3)を右から掛けて変換後の値を得る -5. `CV_8UC3`(8bit整数)に変換して出力する - -- 変換後の値は 0〜255 の範囲に自動クリップされる(`ConvertTo` による飽和). diff --git "a/docs/05_PLAN/PLAN_01_\343\203\252\343\203\225\343\202\241\343\202\257\343\202\277\343\203\252\343\203\263\343\202\260\350\250\210\347\224\273.md" "b/docs/05_PLAN/PLAN_01_\343\203\252\343\203\225\343\202\241\343\202\257\343\202\277\343\203\252\343\203\263\343\202\260\350\250\210\347\224\273.md" deleted file mode 100644 index d8464c1..0000000 --- "a/docs/05_PLAN/PLAN_01_\343\203\252\343\203\225\343\202\241\343\202\257\343\202\277\343\203\252\343\203\263\343\202\260\350\250\210\347\224\273.md" +++ /dev/null @@ -1,103 +0,0 @@ -# リファクタリング計画 (Refactoring Plan) - -## 方針 (Policy) - -- 処理の変更は一切行わない.構造の整理のみを目的とする. -- 各フェーズ完了後に必ずテストを実施し,動作が変わっていないことを確認してから次へ進む. -- テストの具体的な手順は `TEST_01_テスト仕様.md` を参照する. - -## 現状の問題点 (Current Issues) - -`CameraBase.cs` に以下の責務が集中している. - -- カメラ制御の抽象定義(Connect/Disconnect/Shot) -- チャート検出(ArUco マーカー検出,ホモグラフィ計算,マスク生成) -- 色補正処理(TCC行列計算,多項式拡張,画像変換) -- ファイル I/O(CSV読み書き,保存フォルダ生成,撮影情報出力) -- サウンド再生 - -## 目標とするディレクトリ構成 (Target Structure) - -```text -TIASshot/ -├── Cameras/ ← カメラ制御(CameraBase, IScam, Lucam) -├── ChartDetection/ ← チャート検出(CameraBaseから分離) -├── ColorCorrection/ ← 色補正・TCC計算(CameraBaseから分離) -├── UI/ ← フォーム(Form1, PreviewMonitor) -├── Config.cs ← 設定ファイル読み込み(変更なし) -├── LightSource.cs ← 光源制御(変更なし) -└── Program.cs ← エントリポイント(変更なし) -``` - -## フェーズ構成 (Phases) - -### フェーズ1: フォルダ整理とファイル移動 - -**作業内容** - -- 以下のフォルダを作成し,既存ファイルを移動する. - - `Cameras/` ← CameraBase.cs, IScam.cs, Lucam.cs - - `UI/` ← Form1.cs, Form1.Designer.cs, Form1.resx, PreviewMonitor.cs, PreviewMonitor.Designer.cs, PreviewMonitor.resx -- 名前空間(namespace)はそのまま `TIASshot` で統一する. -- ファイルの中身は変更しない. - -**テスト** - -- ビルドが成功することを確認する. -- アプリが起動し,カメラが接続されることを確認する(手動). - -### フェーズ2: 色補正処理の分離 - -**作業内容** - -- `ColorCorrection/` フォルダを作成する. -- CameraBase から以下を新クラス `ColorCorrector` に切り出す. - - フィールド: `TCC_SRGB`, `TCC_XYZ`, `ChannelList`, `ExtendChannels`, `UpdateRate` - - メソッド: `ExtendMat`, `CalcConvertMatrix`, `ConvertColor`, `ConvertImage` - - メソッド: `CalcTcc`(チャート RGB 値の受け取りと行列計算・保存を担当) -- CameraBase は `ColorCorrector` を保持し,処理を委譲する. - -**テスト** - -- ビルドが成功することを確認する. -- 回帰テスト: 同一入力画像から算出した変換行列が,リファクタリング前と一致することを確認する. - - 具体的な手順は `TEST_01_テスト仕様.md` の「回帰テスト: 色補正処理」を参照する. - -### フェーズ3: チャート検出処理の分離 - -**作業内容** - -- `ChartDetection/` フォルダを作成する. -- CameraBase から以下を新クラス `ChartDetector` に切り出す. - - フィールド: `ARDict`, `PointsDst40`, `PointsDst41`, `_detectionCount`, `_lastPosition` - - メソッド: `DetectChart` - - 出力: チャートマスクリスト(`List`)を戻り値または引数で渡す -- CameraBase は `ChartDetector` を保持し,処理を委譲する. - -**テスト** - -- ビルドが成功することを確認する. -- 回帰テスト: 保存済みの実画像を入力として,チャートマスクの位置が一致することを確認する. - - 具体的な手順は `TEST_01_テスト仕様.md` の「チャート検出テスト」を参照する. - -### フェーズ4: ファイル I/O・サウンドの分離 - -**作業内容** - -- CameraBase から以下をユーティリティクラスとして切り出す(配置先は `Cameras/` または新フォルダ). - - `LoadMatFromCsv`, `SaveMatToCsv` - - `SetSaveFolder`, `WriteInfo` - - `EventSound` - -**テスト** - -- ビルドが成功することを確認する. -- 撮影からファイル保存までの一連の動作を手動で確認する. -- 保存された CSV・画像の内容がリファクタリング前と一致することを確認する. - -## 共通ルール (Common Rules) - -- 各フェーズは独立したブランチではなく,`refactor/code-cleanup` ブランチ上で順に進める. -- フェーズ完了ごとにコミットする.コミットメッセージは `[clean]` タグを使用する. - - 例: `[clean] 色補正処理を ColorCorrector クラスに分離` -- テストが通らない場合は,次のフェーズに進まない. diff --git "a/docs/05_PLAN/PLAN_02_\343\203\220\343\202\260\344\277\256\346\255\243\350\250\210\347\224\273.md" "b/docs/05_PLAN/PLAN_02_\343\203\220\343\202\260\344\277\256\346\255\243\350\250\210\347\224\273.md" deleted file mode 100644 index 2d3d5fb..0000000 --- "a/docs/05_PLAN/PLAN_02_\343\203\220\343\202\260\344\277\256\346\255\243\350\250\210\347\224\273.md" +++ /dev/null @@ -1,115 +0,0 @@ -# バグ修正計画 (Bug Fix Plan) - -## 前提 (Prerequisites) - -- `PLAN_01_リファクタリング計画.md` の全フェーズが完了し,動作がリファクタリング前と同一であることを確認してから着手する. -- 修正は1件ずつ独立して行い,都度テストを実施する. -- コミットメッセージは `[fix]` タグを使用する. - - 例: `[fix] _shots へのスレッドセーフなアクセスに修正` - -## 症状 (Symptom) - -- 連続撮影で4枚以上取得するとアプリがたまにクラッシュする. -- 再現性は不安定(毎回ではなく「たまに」落ちる). - -## 原因と修正方針 (Causes and Fix Policy) - -### 優先度1: `_shots` へのスレッドセーフでないアクセス(根本原因) - -**原因** - -`_shots`(`List`)は Shot スレッドと SaveThread から同時にアクセスされる. -`List` はスレッドセーフではないため,以下の2点で競合が発生する. - -- リサイズ競合 - - `List` の内部配列は容量が足りなくなると自動で拡張される(デフォルト容量 4 → 5枚目の Add 時に 8 に拡張). - - 拡張中に SaveThread が `_shots[saveCount]` を読むとメモリ破壊が起きる. - - 「4枚以上でたまに落ちる」という症状に一致する. -- TOCTOU(確認と使用の間の競合) - - SaveThread の `_shots.Count` チェックと `_shots[saveCount]` アクセスの間に Shot スレッドがリサイズを起こすと `IndexOutOfRangeException` が発生する. - -**修正方針** - -- `_shots` を `List` から `System.Collections.Concurrent.ConcurrentQueue` またはロック付きアクセスに変更する. -- SaveThread の待機ロジックをスレッドセーフな方法(`SemaphoreSlim` 等)に変更する. - -**テスト** - -- 連続撮影を10枚以上で複数回繰り返し,クラッシュしないことを確認する. - -### 優先度2: `ConvertImage` 内の Mat 未 Dispose によるメモリ蓄積 - -**原因** - -`ConvertImage` 内で生成される中間 Mat(`extended`,`converted`,`convertedImage`)が Dispose されないまま残る. -1回の呼び出しで数百 MB〜1 GB 超が蓄積し,画像枚数×チャンネル数分だけ積み重なる. -(例: IScam 1840×2440,17次元,4枚連続撮影 → 数十 GB 規模) - -**修正方針** - -- `extended`,`converted`,`convertedImage` を `using` ブロックで管理して確実に Dispose する. - -**テスト** - -- タスクマネージャでメモリ使用量を監視しながら連続撮影を繰り返し,撮影ごとにメモリが増加し続けないことを確認する. - -### 優先度3: `Parallel.For` 内での未捕捉例外 - -**原因** - -`ExtendMat` の `Parallel.For` 内で OOM 等の例外が発生した場合,`AggregateException` としてラップされて SaveThread に伝播する. -SaveThread で捕捉されなければアプリがクラッシュする. -※ 優先度2の修正でメモリ蓄積が解消されれば,発生頻度は大幅に下がる. - -**修正方針** - -- SaveThread に例外ハンドラを追加し,クラッシュではなくエラーメッセージを表示して処理を中断するようにする. -- `ExtendMat` の `Parallel.For` に try-catch を追加する. - -**テスト** - -- 例外発生時にアプリが落ちずエラーが表示されることを確認する. - -### 優先度4: `ConvertImage` による `_shots[i]` の in-place 書き換え - -**原因** - -`ConvertImage` 内の `src.ConvertTo(src, CV_64FC3)` が `_shots[i]` 自体を CV_8UC3 から CV_64FC3 に書き換える副作用がある. -特に IScam の `new Mat(imgt_full, _roi)`(サブマトリクス)に対して in-place の型変換を行うと不正な動作を引き起こす可能性がある. -また複数チャンネルを処理する際,2回目以降は変換済みのデータに対して処理をかけることになり,変換結果が誤る. - -**修正方針** - -- `ConvertImage` の冒頭で `src` をコピーし,コピー側を変換するようにする(`src` を直接書き換えない). - -**テスト** - -- 複数チャンネル(例: `4,10,17`)で撮影し,各チャンネルの sRGB 変換画像が正しく出力されることを確認する. - -### 優先度5: Mat 未 Dispose によるメモリリーク - -**原因 A: `_shots.Clear()` で Mat が解放されない** - -- 撮影ごとに `_shots.Clear()` が呼ばれるが,格納された Mat は Dispose されない. - -**原因 B: `_chartMasks.Clear()` で Mat が解放されない** - -- チャート再検出時に `_chartMasks.Clear()` が呼ばれるが,同様に Dispose されない. - -**修正方針** - -- Clear 前に各 Mat を Dispose するヘルパーを追加する. - -**テスト** - -- タスクマネージャでメモリ使用量を監視し,撮影・キャリブを繰り返してもメモリが増加し続けないことを確認する. - -## 修正順序まとめ (Summary) - -| 順位 | 原因 | クラッシュとの関連 | -| --- | --- | --- | -| 1 | `_shots` のスレッドセーフでないアクセス | 直接原因(「たまに落ちる」に一致) | -| 2 | `ConvertImage` 内の Mat 未 Dispose | 直接原因(OOM によるクラッシュ) | -| 3 | `Parallel.For` 内の未捕捉例外 | 間接原因(2の修正で頻度減) | -| 4 | `ConvertImage` の in-place 書き換え | 誤動作(変換結果の誤り) | -| 5 | `_shots` / `_chartMasks` の Mat 未 Dispose | メモリリーク(蓄積) | diff --git "a/docs/05_TECH/TECH_01_\343\202\257\343\203\251\343\202\271\350\250\255\350\250\210.md" "b/docs/05_TECH/TECH_01_\343\202\257\343\203\251\343\202\271\350\250\255\350\250\210.md" new file mode 100644 index 0000000..b861ef5 --- /dev/null +++ "b/docs/05_TECH/TECH_01_\343\202\257\343\203\251\343\202\271\350\250\255\350\250\210.md" @@ -0,0 +1,87 @@ +# クラス設計 (Class Design) + +## クラス一覧 (Class Overview) + +**CameraBase(抽象基底クラス)** + +- ファイル: `CameraBase.cs` +- カメラに共通する処理をまとめた抽象クラス. +- IScam・Lucam の両方が継承する. + +**IScam** + +- ファイル: `IScam.cs` +- ImagingSource カメラ(DFK33UX178,DFK23UX249)の実装. +- IC Imaging Control SDK(`TIS.Imaging`)を使用する. + +**Lucam** + +- ファイル: `Lucam.cs` +- Lumenera カメラ(Lw110)の実装. +- Lumenera SDK(`lumenera.api.dll`)を使用する. + +**Form1** + +- ファイル: `Form1.cs` +- メインウィンドウ.UI イベントの受け取りとカメラ・光源の操作を担う. + +**PreviewMonitor** + +- ファイル: `PreviewMonitor.cs` +- サブモニタへのプレビュー表示フォーム. + +**Config(静的クラス)** + +- ファイル: `Config.cs` +- `config.xml` の読み込みユーティリティ. +- XPath を使って文字列・整数・浮動小数点数の値を取得する. + +**LightSource** + +- ファイル: `LightSource.cs` +- シリアル通信(USB-シリアル変換)による光源の ON/OFF 制御. + +## 継承関係 (Inheritance) + +```text +CameraBase(抽象) +├── IScam ← ImagingSource カメラ +└── Lucam ← Lumenera カメラ +``` + +## CameraBase の主なメンバ (CameraBase Members) + +### 抽象メソッド(派生クラスで実装する) + +- `Connect() : bool`: カメラに接続する +- `Disconnect()`: カメラを切断する +- `Shot(numImages, interval)`: 撮影処理 + +### 公開メソッド + +- `ShotOne()`: 1枚撮影 +- `ShotMulti()`: 連続撮影 + +### 保護メソッド(派生クラスから呼ぶ) + +- `BootCheck() : bool`: 起動時の前提条件チェック(設定・TCC参照ファイル) +- `DetectChart(img)`: ArUco マーカーによるチャート検出 +- `CalcTcc(img)`: TCC 変換行列の計算・保存 +- `ConvertImage(src, conv) : Mat`: 画像の色変換 +- `SaveThread(numImages)`: 画像保存スレッド処理 +- `GetRatio(value, target) : float`: ゲイン更新比率の計算 + +### 主なフィールド + +- `_calibrating`: キャリブレーション残りフレーム数(0 で完了) +- `_calibrated`: キャリブレーション完了フラグ +- `_chartMasks`: 24色票の ROI マスクリスト +- `_convRGB2SRGB`: 次元数をキーとした RGB→sRGB 変換行列の辞書 +- `_roi`: ROI 矩形(IScam のみ使用) +- `_shots`: 撮影済み画像バッファ + +## Form1 とカメラの関係 (Form1 and Camera) + +- Form1 は `CameraBase` 型の `_camera` フィールドを持つ. +- 起動時に Lucam → IScam の順で接続を試みる(詳細は `TECH_02` 参照). +- UI スレッドとカメラスレッドをまたぐメソッド(`ShowMessage`,`EnableShots` 等)は `InvokeRequired` を確認してスレッドセーフに呼び出す. diff --git "a/docs/05_TECH/TECH_02_\343\202\253\343\203\241\343\203\251\345\210\266\345\276\241\344\273\225\346\247\230.md" "b/docs/05_TECH/TECH_02_\343\202\253\343\203\241\343\203\251\345\210\266\345\276\241\344\273\225\346\247\230.md" new file mode 100644 index 0000000..adec6b7 --- /dev/null +++ "b/docs/05_TECH/TECH_02_\343\202\253\343\203\241\343\203\251\345\210\266\345\276\241\344\273\225\346\247\230.md" @@ -0,0 +1,88 @@ +# カメラ制御仕様 (Camera Control Specification) + +## 起動時のカメラ接続フロー (Startup Connection Flow) + +Form1 ロード時に以下の順で接続を試みる. + +1. `Lucam.Connect()` を実行する + - 成功: Lucam をアクティブカメラとして使用する + - 失敗: 次へ + +2. ImagingSource デバイス一覧を走査する + - 接続されているデバイス名を順に `IScam.Connect()` で試行する + - 最初に成功したカメラをアクティブカメラとして使用する + +3. どのカメラも接続できない場合 + - エラーダイアログを表示してアプリを終了する + +## IScam(ImagingSource カメラ)の制御 (IScam Control) + +### 接続 + +- `[デバイス名].xml` からカメラ設定をロードする. +- `config.xml` の `<カメラ名>/ROI` から ROI 設定を読み込む. +- `FrameQueueSink` をシンクに設定してライブ開始する. + +### プレビュー(フレーム取得コールバック) + +- `FrameQueueSink` の `Retrieve` コールバックでフレームを受け取る. +- 受け取った画像を転置(`.T()`)し,ROI を切り出して表示する. +- キャリブレーション中は白色票の測定・ゲイン更新も行う(後述). + +### ROI クリップ + +- ImagingSource カメラは横向きに画像を出力するため,転置して縦向きに変換する. +- 転置後の画像座標系で ROI(X,Y,W,H)を切り出す. + +### 撮影 + +- シンクを `FrameQueueSink` → `FrameSnapSink` に切り替える. +- `SnapSingle()` で1フレームずつ取得して `_shots` に蓄積する. +- 撮影完了後,シンクを `FrameQueueSink` に戻す. +- 最初の1フレームは捨てる(カメラの安定待ち). + +## Lucam(Lumenera カメラ)の制御 (Lucam Control) + +### 接続 + +- `LucamNumCameras()` で接続台数を確認する(0台または2台以上はエラー). +- カメラ ID から機種を特定する(`0x49f` → Lw110). +- `LucamRgbPreviewCallback` コールバックを登録する. +- `config.xml` の `` からカメラパラメータを設定して,プレビューを開始する. + +### プレビュー(フレーム取得コールバック) + +- `LucamRgbPreviewCallback` でフレームを受け取る. +- 受け取った画像を転置(`.T()`)して表示する(ROI クリップなし). +- キャリブレーション中は白色票の測定・ゲイン更新も行う(後述). + +### 撮影 + +- `LucamEnableFastFrames()` でスナップ撮影モードに切り替える. +- `LucamTakeFastFrame()` でRAW画像を取得し,`LucamConvertFrameToRgb24()` でRGB変換する. +- 撮影完了後,`LucamDisableFastFrames()` で通常モードに戻す. + +## ホワイトバランス自動調整 (White Balance Calibration) + +チャート検出後(`_calibrating` が `Calib/Frames` にセットされた後),各フレームで以下の処理を行う. + +1. 白色票(`_chartMasks[12]`,24色票の13番)の平均 RGB 値を測定する + +2. `Calib/UpdateInterval` フレームごとにゲインを更新する + - 更新比率: `ratio = (target / current - 1) * UpdateRate + 1` + - IScam: `VCDRangeProperty.Value` を直接更新する + - Lucam: `_snap.Gain*` を更新し `SetCameraParam()` で反映する + +3. `_calibrating` をデクリメントし,0 になったら `CalcTcc()` を呼び出す + +## データ保存フォルダの生成 (Save Folder) + +`SetSaveFolder(note)` が保存先フォルダを以下の書式で生成する. + +```text +[SaveFolder]/[yyyy-MM-dd]/[HH_mm_ss]-[note]/ +``` + +- `SaveFolder` は `config.xml` の `` で指定する. +- `note` は「データ名」(撮影時)または「校正」(キャリブ時). +- フォルダが存在しない場合は自動作成する. diff --git "a/docs/05_TECH/TECH_03_\350\211\262\350\243\234\346\255\243\345\207\246\347\220\206\344\273\225\346\247\230.md" "b/docs/05_TECH/TECH_03_\350\211\262\350\243\234\346\255\243\345\207\246\347\220\206\344\273\225\346\247\230.md" new file mode 100644 index 0000000..c320182 --- /dev/null +++ "b/docs/05_TECH/TECH_03_\350\211\262\350\243\234\346\255\243\345\207\246\347\220\206\344\273\225\346\247\230.md" @@ -0,0 +1,85 @@ +# 色補正処理仕様 (Color Correction Specification) + +## 処理の全体フロー (Overall Flow) + +```text +1. ArUco マーカー検出(DetectChart) + ↓ +2. チャート固定判定 + ↓ +3. ホワイトバランス自動調整(ゲイン更新) + ↓ +4. TCC 変換行列の計算(CalcTcc) + ↓ +5. 変換行列の保存・撮影の有効化 +``` + +## チャート検出(DetectChart) (Chart Detection) + +### ArUco マーカー検出 + +- 辞書: Dict4X4_50(4×4 ビット,50種) +- 使用マーカー ID: 40(チャート上端),41(チャート下端) +- 両方のマーカーが検出できない場合は処理をスキップする. +- ID40 の Y 座標が ID41 より大きい場合(上下逆)は警告を表示する. + +### チャート固定判定 + +- ID40 の検出座標と前フレームの座標の距離を計算する. +- 距離 < `Calib/ChartSetCriteria` が `Calib/ChartSetCount` フレーム連続したら固定と判断する. +- 動きを検出したらカウントをリセットする. + +### ホモグラフィ計算とチャートマスク作成 + +- 2つのマーカーの角点(計8点)からホモグラフィ行列を計算する. +- ホモグラフィで画像を 1545×810 px に正面化する. +- 正面化画像上で 24 色票の ROI を定義し(6列×4行,各 80×80 px),元の画像座標系に逆変換してマスクを生成する. +- 生成したマスクを `_chartMasks` リスト(インデックス 0〜23)に格納する. + +## TCC 変換行列の計算(CalcTcc) (TCC Matrix Calculation) + +### チャート RGB 値の取得 + +- `_chartMasks` を使って 24 パッチの平均 RGB 値を測定する(24×3 行列). + +### 多項式拡張(ExtendMat) + +3チャンネル(R,G,B)を指定の次元数に拡張する. + +**次元数と項の対応** + +| 次元数 | 追加される項 | +| --- | --- | +| 4 (1次式) | 1, R, G, B | +| 10 (2次式) | + RG, RB, GB, R², G², B² | +| 17 (3次式) | + R²B, R²G, G²R, G²B, B²R, B²G, RGB | + +- 各項は `ExtendChannels` テーブルで定義した3チャンネルの積として計算する. + +### 変換行列の算出(CalcConvertMatrix) + +- 拡張行列(24×N)と目標行列(24×3)から最小二乗法(SVD分解)で変換行列(N×3)を算出する. +- `Cv2.Solve(..., DecompTypes.SVD)` を使用する. + +### 算出する変換行列の種類 + +- RGB→sRGB: カメラ RGB から sRGB への変換行列 +- RGB→XYZ: カメラ RGB から XYZ 色空間への変換行列 +- sRGB→XYZ: sRGB から XYZ 色空間への変換行列 +- 各行列は指定した次元数ぶん計算・保存される. + +### TCC参照値ファイル + +- `tcc_srgb.csv`: 24色票の標準 sRGB 値(24×3 行列) +- `tcc_xyz.csv`: 24色票の標準 XYZ 値(24×3 行列) +- アプリ起動時に読み込み,`CameraBase` に保持する. + +## 画像の色変換(ConvertImage) (Image Color Conversion) + +1. 入力画像を `CV_64FC3`(64bit浮動小数点3チャンネル)に変換する +2. 画像を1次元(ピクセル数×3)に変形(Reshape)する +3. 多項式拡張(ExtendMat)でピクセル数×N 行列にする +4. 変換行列(N×3)を右から掛けて変換後の値を得る +5. `CV_8UC3`(8bit整数)に変換して出力する + +- 変換後の値は 0〜255 の範囲に自動クリップされる(`ConvertTo` による飽和).