
顕微鏡の動画からアノテーションを自動でしている様子
※対象の花粉種は著作権・公開上の都合で伏せます。
1.何をしたいか
顕微鏡動画を眺めながら花粉(ここでは「青い円粒子」)を数えるのは、地味に大変です(やったことないので予想ですが)
- そもそも数が多い
- 粒子が動く / ピントが揺れる
- 視野の縁や減光で、円っぽい偽検出が出る
- 照明ムラで色が不安定になる
そこで今回は、顕微鏡動画/画像から「青い円粒子」を検出し、追跡してユニーク数(≒個数)を数えるコードをまとめます。
特に、顕微鏡特有の「視野の縁(FOV境界)に出る偽円」を抑えるために、v2では FOV距離Transform + 円周内率ゲートを入れて堅牢化しています。
2.出力できるもの
このスクリプトは、次を自動で吐きます。
- フレームごとの検出結果(赤枠/塗りつぶし)
- 追跡ID付きの可視化(緑円 + ID)
- 追跡ログ(
tracks.csv/detections_history.csv) - 各IDの“ベストな切り出し画像”(best crop)
- デバッグ画像一式(FOVマスク、青スコア、2値マスク、分離結果など)
- 実行環境とパラメータのログ(run_log.json)
「ちゃんと動いてるか」を後で確認しやすいのが狙いです。
3.アプローチ概要(パイプライン)
大きくは 5段です。
(1) 視野(FOV)を推定して、外側や暗部を落とす
- 明るい領域を視野とみなしてマスク化(LABのLでOtsu → 最大連結成分)
- Closeで穴埋め、Erodeで縁を削る
- さらに暗部(減光・黒縁) をしきい値で落とす
これで「そもそも検出してはいけない場所」を最初に消します。
(2) 照明ムラを補正(前処理)
- Lチャンネルの背景(Morph Open)を引いて正規化
- CLAHEで局所コントラストを補う
- 仕上げに bilateral でノイズを抑える
顕微鏡動画は照明ムラが支配的なので、青検出の前に明るさを整えるのが効きます。

(3) 「青らしさ」を連続スコアで作る
単純なBGR閾値ではなく、以下を混ぜます。
- HSV:色相(hue)を中心値(例:115)からの距離でガウス評価 + S/Vの下限ゲート
- LAB:b成分(黄↔青)の青側を評価
- Excess Blue:2B - G - R で青優位を抽
最後に Top-hat を少し足して小さな粒子を拾いやすくします。
(4) Otsuで2値化 → くっつきをWatershedで分離
-
スコアをOtsuでしきい値化(otsu_scale で微調整)
-
Open/Closeで整形
-
Watershedで接触粒子を分離
-
(5) 円候補を作り、信頼度(conf)でふるい、追跡する
候補は2系統: -
輪郭(contour)→最小外接円 + 円形度
-
HoughCircles(補助)
そして候補ごとに
- 内外リングのコントラスト
- 円周のスコア
- エッジ強度
- HSVの色の強さ
などの軽量な特徴量から conf を作り、conf_thresh で落とします。
最後に centroid tracking でIDをつけ、**“ユニークな粒子数”**として数える準備が整います。
4.v2 tuned の貢献点
本実装は「理論的新規性」より、顕微鏡実データで破綻しないように “縁由来の偽検出”を系統的に潰す設計に価値があルと思います。
- 顕微鏡視野の境界由来アーティファクトを抑えるため、FOV距離Transformと円周内率を用いた スケール依存ゲーティング(dist >= factor*r および inside_ratio)を導入。
- しきい値をコマンドライン引数として公開し、デバッグ出力・ログ出力・best crop保存まで含めた 運用可能な検出・追跡パイプラインとして整理。
5.もし他に転用をかんがえているなら
[いまのままのコードで使う」ならば、つまみを変更してください(重要)
縁の偽検出が多いとき
-
--fov-border-factorを上げる(例: 1.35 → 1.45) -
--fov-inside-ratioを上げる(例: 0.95 → 0.96) -
--fov-marginを増やす(縁を削る)
検出が減りすぎるとき
-
--conf-threshを下げる(例: 0.32 → 0.30) -
--otsu-scaleを下げる(例: 1.05 → 1.00)
暗い領域の誤検出が目立つとき
-
--dark-v-min / --dark-l-minを上げる
- 実行方法(例)
依存:
pip install opencv-python numpy pandas
動画で実行:
python blue_circle_track_scientific_fov_v2_tuned.py --video "xxx.mov" --save-mode detect
縁がうるさいとき:
python blue_circle_track_scientific_fov_v2_tuned.py --video "xxx.mov" --fov-border-factor 1.45 --fov-inside-ratio 0.96
検出が少ないとき:
python blue_circle_track_scientific_fov_v2_tuned.py --video "xxx.mov" --conf-thresh 0.30 --otsu-scale 1.00
7.出力フォルダの見どころ
実行するとresults_xxx/に以下ができます。
-
annotated/:追跡IDつき(緑円 + ID) -
red_outline/:検出円だけ赤枠 -
red_filled_only/:検出領域を赤で塗る(視認性高い) -
debug/:-
fov0_*.jpg:視野推定(生のFOV) -
fov_*.jpg:暗部除去後のFOV -
score_*.jpg:青スコア -
mask0_*.jpg:2値マスク -
split_*.jpg:Watershed分離結果 -
fovdist_*.jpg:FOV距離Transform(縁ゲート確認に便利)
-
-
best_crops/:各IDのベスト切り出し(目視確認が爆速) -
tracks.csv/detections_history.csv run_log.json
8.なぜ「FOV距離Transform + inside_ratio」が効くのか
顕微鏡動画で“円っぽい偽物”が出る最大の温床は 視野の縁です。
- 視野外の黒
- 減光(ビネット)
- 反射・リング状のハレーション
- カメラの圧縮ノイズや微小なゴミ
これらは円に見えやすい一方、画面中心の粒子とは性質が違います。
そこで v2 では
-
中心点が境界から十分離れていること
distanceTransform(fov) >= factor*r
→ 大きい円ほど「より内側にいるべき」と要求できる(スケール依存) -
円周が視野内に十分含まれていること
円周上をサンプルして inside 率を計算し、inside_ratio 未満は落とす
という2段ゲートで、縁由来の偽円を“構造的に”落とします。
パラメータは増えますが、誤検出が出る理由が明確なので調整しやすいのが実務では大事です。
9.限界と、期待値
この手法は「青い円粒子」を対象にしているため、次の条件では調整が必要です。
- 対象の色が青からズレる(染色・照明・ホワイトバランス)
- 粒子が真円でなくなる(潰れ・重なり・ピント外れ)
- 視野推定(FOV)が崩れる(背景が明るい、視野が複数、極端な照明ムラ)
ただ、デバッグ画像を自動で保存しているので、崩れ方は追いやすいです。
「まず debug を見る」だけで原因特定がかなり早くなります。
10. 実装の要点(コード抜粋)
FOV距離Transformによる縁ゲート(v2の核)
fov_dist = cv2.distanceTransform((fov > 0).astype(np.uint8) * 255, cv2.DIST_L2, 3)
d_border = float(fov_dist[iy, ix])
need = max(fp.border_min_px, fp.border_factor * r)
if d_border < need:
continue
円周のFOV内率(inside_ratio)
in_ratio = circle_inside_ratio_on_fov(fov, cx, cy, r, samples=72)
if in_ratio < fp.inside_ratio:
continue
“ベスト切り出し”を残す(現場で便利)
if conf > best_conf:
best_crop = crop
この3つだけでも、顕微鏡動画の「見直しコスト」が一気に下がるかも知れません。
11.コード全文について
長いので、今後githubに掲載予定。
12.まとめ
顕微鏡動画の花粉カウントは、単純なしきい値やHoughだけだと **“縁の偽検出”**で破綻しがちです。
本パイプラインはそこを正面から扱い、
- FOV推定 + 暗部除去
- FOV距離Transform(スケール依存ゲート)
- 円周 inside_ratio(境界にかかった偽円の除去)
- 青スコアの統合(HSV+LAB+ExcessBlue)
- 追跡 + best crop + ログ出力