Ultralytics YOLO27: ウェイトリストに登録する。
Get Started
コンピュータービジョンの基礎·問題を定義する·レッスン 1/10
レッスン初級

問題を定義する

漠然とした製品アイデアを3行のタスク仕様に落とし込み、再学習せずに変更できるよう、ビジネスルールをコードに記述します。

日差しのある都市の交差点を上空から見た画像。歩行者、自転車、バス、自動車を囲むUltralytics YOLOのボックス

明確な問いがないまま停滞するコンピュータービジョンプロジェクトは少なくありません。「危険な行動を検出する」は要件のように聞こえますが、アノテーターはラベル付けできず、モデルは学習できず、誰も正常に機能する基準を判断できません。3行の仕様を作れば、午後のうちにこの問題を解決でき、学習するタスク、ラベル付けする内容、報告する指標も決まります。

成果

3行のタスク仕様(入力、出力、判定)を作成し、YOLO26の出力を読み取るコードとして判定を実装します。

短期集中コース
すでに使い方をご存じの方は、こちらをご覧ください。
  1. 入力:ソース、解像度、フレームレート、シーン。

  2. 出力:ラベル、ボックス、マスク、キーポイント、ピクセル単位のマップなどのデータ型。動詞は使いません。

  3. 判定:誰が、いつ、どのルールに基づいて行動するか。

  4. モデルが担うのは出力の行です。判定の行は、再学習せずに変更できるコードです。

  5. 信頼する前に、実際の出力でルールをテストしてください。画像上の重なりは、物理的な近さを意味しません。

実践的

仕様を最初に作る理由#

コンピュータービジョンプロジェクトのステップ0~4:基礎の構築、データの収集と準備、特徴量エンジニアリングとモデル開発、モデルのデプロイ、モデルの監視

コンピュータービジョンプロジェクトの各ステップは、ステップ0で設定した目標を引き継ぎます。曖昧な問いは、曖昧なデータセット、意見の一致しないアノテーター、誤った対象を測定する指標につながります。ステップ2でどれだけ学習しても、それを修正することはできません。

3行の仕様#

タスク仕様は3つの文で構成し、1行に1文ずつ記述します。それぞれ、プロジェクトに関わる別の担当者が尋ねる質問に答えます。

行答える質問不十分適切
入力モデルには何が見えますか?「カメラ映像」「12台の天井カメラから取得する、1920×1080、5 fpsのフレーム」
出力どのようなデータが返されますか?「安全上の問題を検出する」「forkliftとpersonのボックス。それぞれに信頼度スコアを付与」
判定誰が、いつ行動しますか?「安全性を高める」「フォークリフトと人が2秒間、2 m以内にいる場合、シフト責任者に通知する」

出力の行は特に慎重に決める必要があります。データ型によってタスクが決まるためです。

Ultralytics YOLOの7つのタスク出力を並べた画像:検出、姿勢推定、セグメンテーション、分類、OBB、セマンティックセグメンテーション、深度

画像全体に対するラベルなら分類、ボックスなら検出、輪郭ならセグメンテーション、関節なら姿勢推定、回転ボックスならOBB、ピクセルごとのクラスまたは距離ならセマンティックセグメンテーションまたは深度推定です。次のレッスンでは7つすべてを解説します。

例:モデルとルールを分ける#

「倉庫内でフォークリフトが危険な動きをしているかどうかを把握したい」

これはタスクではなく、プロジェクトの説明です。ピクセルに関わるフォークリフトや人の検出と、何を危険とみなすかというポリシー上の判定という、2つの作業が混在しています。切り分けてみましょう。

モデルとなるのは検出器だけです。ひし形の部分は通常のコードです。今すぐこの切り分けを試せます。bus.jpgにはフォークリフトは写っていませんが、バスと4人の人で同じテストができます。最初のルールとして、「人のボックスの30%を超える部分が、車両のボックスと重なったらアラートを出す」とします。

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
result = model("https://ultralytics.com/images/bus.jpg", verbose=False)[0]

# The model's job ends here: a class, a box, and a confidence per object.
people = [b.xyxy[0].tolist() for b in result.boxes if result.names[int(b.cls)] == "person"]
vehicles = [b.xyxy[0].tolist() for b in result.boxes if result.names[int(b.cls)] == "bus"]

def share_inside(a, b):
    """Fraction of box a's area that lies inside box b."""
    w = max(0, min(a[2], b[2]) - max(a[0], b[0]))
    h = max(0, min(a[3], b[3]) - max(a[1], b[1]))
    return w * h / ((a[2] - a[0]) * (a[3] - a[1]))

# The decision rule is plain code: change the policy without retraining.
ALERT_SHARE = 0.30
for person in people:
    share = max(share_inside(person, v) for v in vehicles)
    print(f"person at x={person[0]:3.0f}: {share:4.0%} inside the bus box -> {'ALERT' if share > ALERT_SHARE else 'ok'}")
person at x= 49:  70% inside the bus box -> ALERT
person at x=223:  76% inside the bus box -> ALERT
person at x=670:  70% inside the bus box -> ALERT
person at x=  0:  62% inside the bus box -> ALERT

アラートは4件、すべて誤検知でした。人々は、停車中のバスの前方にある歩道にいます。検出器はすべてのボックスを正しく検出していますが、ルールが誤っています。2D画像上の重なりは、現実世界で2つの物体が近くにあることを意味しないためです。ルールはコードなので、コードを修正すれば解決できます。各人の足元とバスの車輪のラインの位置を比較します。

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
result = model("https://ultralytics.com/images/bus.jpg", verbose=False)[0]
people = [b.xyxy[0].tolist() for b in result.boxes if result.names[int(b.cls)] == "person"]
vehicles = [b.xyxy[0].tolist() for b in result.boxes if result.names[int(b.cls)] == "bus"]

def same_ground_line(person, vehicle, tolerance=40):
    """True when the person's feet are level with the vehicle's wheels and inside its width."""
    feet_x, feet_y = (person[0] + person[2]) / 2, person[3]
    return vehicle[0] < feet_x < vehicle[2] and abs(feet_y - vehicle[3]) < tolerance

for person in people:
    alert = any(same_ground_line(person, v) for v in vehicles)
    print(f"person at x={person[0]:3.0f}: feet y={person[3]:.0f}, bus wheels y={vehicles[0][3]:.0f} -> {'ALERT' if alert else 'ok'}")
person at x= 49: feet y=902, bus wheels y=751 -> ok
person at x=223: feet y=861, bus wheels y=751 -> ok
person at x=670: feet y=880, bus wheels y=751 -> ok
person at x=  0: feet y=871, bus wheels y=751 -> ok

全員の足元は車輪のラインより110~150ピクセル下にあるため、バスよりカメラに近い位置に立っています。再学習も再ラベル付けも必要ありません。検出器を固定したまま、安全チームがルールを改善できます。本番環境では、キャリブレーション済みカメラで足元を床面座標に変換し、物体追跡で2秒間の継続性を追加します。いずれも同じボックスを使ったコードで実現できます。

ポリシーをモデルの外に出す

主観的な言葉である「unsafe(危険)」「defective(欠陥あり)」「suspicious(疑わしい)」「VIP」は、モデルの出力を読み取るコードに含めます。モデルはピクセルに関する質問に答えます。2人の専門家がラベルについて意見を異にするなら、その議論はバージョン管理、テスト、変更ができるルールに含めます。

よくある落とし穴#

出力行は動詞にする

症状: 仕様に「安全上の問題を検出する」や「シーンを理解する」と書かれていて、アノテーターが何を描けばよいか質問します。対処法: 出力を、名前付きクラスを含むデータ型として書き直します。「forklift と person のボックス」のようにします。

指し示せないクラス

症状: 同じフレームに対して2人のアノテーターが異なるラベルを付ける、suspicious_behavior や dangerous のようなクラスがあります。対処法: 制限区域内にいる人や、開いた状態で固定されたドアなど、目で確認できる根拠に置き換え、判断は意思決定ルールに移します。

出力が文章や予測になっている

症状: 「何が起きているか説明する」(キャプション生成)や「フォークリフトが人にぶつかるか予測する」(予測)になっています。対処法: まず物体を特定し、その上にトラッキングと動作ルールを追加します。予測の前に検出器を構築します。

仕様から成功基準へ#

仕様で定めるのはモデルが何をするかであり、どの程度なら十分か、見逃しと誤報のコストをどう比較するかではありません。Building High-Performance YOLO Datasetsのビジネス目標から始めるでは、仕様を測定可能な目標に落とし込みます。このコースの以降のレッスンでは、3行の仕様を参照します。

試してみる

自分のプロジェクト、または棚の空きスペースを検知する店舗カメラを題材に、3行の仕様を作成してください。各行は1文にまとめます。次に、判定の行にある言葉のうち、モデルがピクセルから認識できないものをすべて列挙し、モデルの出力に対する1行のルールとして判定を記述します(擬似コードでもかまいません)。

完了条件
以下のすべてを満たすと、レッスンは完了です。
  • 入力の行には、ソース、解像度、フレームレートを記載します。

  • 出力の行には、動詞ではなく、名前を付けたクラスを含むデータ型を記載します。

  • 判定は出力に対するルールとして記述し、主観的な言葉はすべてクラス名ではなく、そのルールに含めます。

解答を表示

棚のカメラの仕様例:

  • 入力: 1つの通路に向けた固定カメラから、60秒ごとに取得する1920×1080の静止画。
  • 出力: productとempty_facing(商品がない棚の区画)のバウンディングボックス。それぞれに信頼度スコアを付与します。
  • 判定: 営業時間中に、同じ棚のempty_facingボックスが3つ以上、連続する5枚の静止画にわたって検出された場合、補充タスクを作成します。

モデルが認識できない言葉は、補充タスク、同じ棚、5枚の静止画にわたる継続、営業時間中です。ルールとして記述すると、次のようになります。

if count(empty_facing on shelf S) >= 3 in each of the last 5 stills and store_is_open(now):
    create_restock_task(shelf=S)

店舗は画像を1枚も再ラベル付けせずに、「3」や「5」、営業時間を変更できます。

次のステップ

次のレッスンでは、Ultralytics YOLOがサポートする7つのタスクと、出力の行をいずれか1つのタスクに正確に対応させる方法を学びます。