やってみた 2026年6月30日

話題のsupervisionを使い捨てDockerで動かしてみた——いきなりlibGLで足止めされた話

XECIN PythonコンピュータビジョンDockerOSS

GitHubのトレンドを眺めていたら、roboflow/supervision というリポジトリが目に入った。

説明は「再利用できるコンピュータビジョンの部品を書いておきました」みたいなノリで、スターは4万を超えている。物体検出まわりって、検出結果を描画したり、面積を出したり、ゾーンに入った数を数えたり……毎回ちょっとずつ書き方がブレるコードを書きがちなんですよね。その「部品箱」が本当に使い物になるのか気になって、ちょっと触ってみた。

試した環境は、自分のWindowsマシンの上に使い捨てのDockerコンテナ(python:3.12-bookworm)を立てて、その中だけで完結させた。ホストを汚したくなかったので。バージョンは supervision 0.29.1(2026-06-23リリース)、Python 3.12.13、対象は develop ブランチの最新コミットを見ています。

インストールは一行、なのにimportで止まる

インストールはREADME通り一行だった。

pip install supervision

これ自体は速くて、12秒くらいで終わる。numpy や opencv-python、matplotlib あたりを連れてくる。ここまでは何も考えずにいけた。

で、import するだけのつもりが、いきなりこれ。

$ python -c "import supervision as sv; print(sv.__version__)"
ImportError: libGL.so.1: cannot open shared object file: No such file or directory

正直なところ、一瞬「えっ、入れただけなのに?」と固まった。

エラーをよく読むと、止まっているのは supervision 本体ではなくて、依存している opencv-python が cv2 を読み込むところ。OSのライブラリ(libGL.so.1)を要求していて、素のコンテナにはGUI系のライブラリが入っていないので、そこでコケる。公式のインストール手順は「pip install supervision」だけなんですが、実際やってみると、サーバー寄りの素のイメージだとこの一手間が要る、というやつでした。

解決はOSパッケージを足すだけ。

apt-get update && apt-get install -y libgl1 libglib2.0-0

4秒ほどで終わって、これで import が通った。あらためてバージョンを確認するとこんな感じ。

$ python -c "import supervision as sv, cv2, numpy; print(sv.__version__, cv2.__version__, numpy.__version__)"
0.29.1 4.13.0 2.5.0

モデル無しで「検出結果の部品」を触ってみる

ここからが本題。supervisionは「モデル非依存」を謳っていて、検出結果さえ sv.Detections の形にしてあれば、描画も集計もやってくれる。今回はモデルを落とすと重いので、検出結果を手で作って渡してみた。

import numpy as np, cv2, supervision as sv

xyxy = np.array([[50,60,220,300],[260,120,430,260],[450,80,600,360]], dtype=float)
det = sv.Detections(
    xyxy=xyxy,
    confidence=np.array([0.92, 0.74, 0.55]),
    class_id=np.array([0, 2, 0]),
)
print(len(det), det.area.astype(int).tolist())
# -> 3 [40800, 23800, 42000]

len(det) で件数、det.area で各ボックスの面積(ピクセル)がそのまま取れる。地味ですが、こういう「検出結果に対する操作」がプロパティ一発で済むのは、毎回自前で書いていた身からすると気持ちいい。

次に BoxAnnotatorLabelAnnotator で、真っ黒なキャンバスに枠とラベルを描いてPNG保存。

names = {0: "person", 2: "car"}
labels = [f"{names[c]} {p:.2f}" for c, p in zip(det.class_id, det.confidence)]

scene = np.zeros((480, 640, 3), np.uint8)
out = sv.BoxAnnotator().annotate(scene=scene.copy(), detections=det)
out = sv.LabelAnnotator().annotate(scene=out, detections=det, labels=labels)
cv2.imwrite("annotated.png", out)

吐き出した annotated.png は 640x480 のRGB画像で、読み戻すと shape は (480, 640, 3) の uint8。真っ黒な背景に対して非ゼロ画素が 50796 個あったので、ちゃんと枠とラベルが描かれている、という確認まで取れた。スクショは載せられないけど、数字でも「描けている」ことは分かる。

IoUとゾーン判定もメソッド一発

描画だけじゃなくて、評価でよく使う計算も入っている。正解ボックスと検出のIoUを一括で出す関数を試した。

gt = np.array([[55, 65, 225, 305]], float)
print(np.round(sv.box_iou_batch(gt, xyxy), 3).tolist())
# -> [[0.905, 0.0, 0.0]]

正解にほぼ重なる1個目だけ 0.905、残りは 0.0。期待通りの値が返ってきた。

あと PolygonZone で「画面の左半分に入っている検出だけ数える」みたいなことも、ポリゴンを渡して trigger するだけ。今回は左半分の矩形に対して [True, False, False] が返ってきて、カウントは1だった。監視カメラのライン越えカウントみたいな用途が、これだけで書けそうだなと思った。

ひとつ正直に書いておくと、依存が opencv-python(GUI付きの方)なのは、サーバーやCIみたいにGUIの無い環境だと地味に引っかかる。opencv-python-headless を使いたい場面も多いはずなので、ここは自分の使い方次第で差し替えを考えたいところ。今回みたいに最小コンテナで動かすなら、最初に「libGLを足すか、headlessを別に入れるか」を決めておくと詰まらずに済みます。

試してみての所感

自分はこれを「検出まわりの定型処理の置き場」として、普通に使っていきたいと思った。描画・面積・IoU・ゾーン判定みたいな、毎回ちょっとずつ書き方がブレるところを同じAPIに寄せられるのは、素直に楽です。

一方で、今回はモデルを使わずに検出結果を手で作っただけなので、実際のモデル(YOLO等)と繋いだときの体験はまだ見ていない。そこと、headless運用での詰まりどころは別途試したいところ。依存の重さ(opencvのGUIライブラリ問題)は環境によっては最初の関門になるので、そこだけは事前に潰しておくのがおすすめです。

もっと良い使い方や、headless環境でうまく回している知見があったら、ぜひ教えてください。