AIで画像領域の座標を変換する方法|左上・中心・正規化を分ける

800×600の架空条件と6領域でxywhから正規化中心座標へ変換。画像外・ゼロ幅を分け、元ID、逆変換、クラス対応を確認します。

AIものさし編集部

AIに画像内の領域座標を整理させるときは、四つの数値が何を表すかを先に決めます。左上と右下の位置、左上と幅・高さ、中心と幅・高さは別の形式です。数が四つあるというだけで、そのまま別のツールへ渡せるとは限りません。

この記事では、幅800・高さ600という架空の画像条件で、領域6件を検査し、画像内の3件を正規化した中心座標へ変換します。実画像を用意した例や、AIが物体を検出した結果ではありません。座標と変換コードを手製で作った説明用の例です。

座標形式と、どの画像の寸法かを固定する

今回は、左上を原点とし、右へx、下へyが増える座標を使います。入力はピクセル単位のx、y、幅w、高さhです。画像の境界は0〜800、0〜600の連続した座標として扱い、右端や下端に接する箱も許します。ピクセルの配列添字を指定する例ではありません。

表は横にスクロールできます

形式四つの数値の意味今回の入力例
xywh左上x、左上y、幅、高さ100、150、300、300
xyxy左上x、左上y、右下x、右下y100、150、400、450
cxcywh中心x、中心y、幅、高さ250、300、300、300

Torchvisionの公式説明も、これらの形式を区別しています。形式の変換と、画像寸法で割る正規化を同じ処理と思わないようにします。Torchvision公式:box_convert(新しいタブで開く)

AIが見た画像と、座標を載せる画像の版・寸法が同じかも確認します。縮小、切り抜き、余白追加、回転を行った後へ、以前の座標をそのまま当てないようにしてください。寸法が不明なら、推測して正規化せず確認待ちにします。

変換前に、元の箱を検査する

次が架空の6件です。幅と高さは正の値とし、箱全体が画像内にあることを今回の条件にします。画像外の箱をどう処理するかは用途によるため、この例では自動で切り詰めません。

表は横にスクロールできます

IDxywh今回の判定
B0100200150画像内。左上端に接する
B02600450200150画像内。右下端に接する
B03100150300300画像内
B04750550100100右端850、下端650で画像外
B05100100040幅が0
B06−101010左上xが負

B04を、正規化後の中心が0〜1に入りそうだからという理由だけで通しません。箱の右端x+wと下端y+hも確認します。B05も、四つの数値がそろっているだけでは有効な領域になりません。

この判定は今回の受け渡し条件です。すべてのデータ形式が画像外の領域を同じように拒否するという説明ではありません。範囲へ切り詰める運用なら、元座標、変更後の座標、変更理由を別に残します。

左上から中心を計算してから、寸法で割る

中心のピクセル座標は、x+w÷2、y+h÷2です。その中心と幅を画像幅Wで、中心yと高さを画像高Hで割ります。

center_x = (x + w / 2) / W
center_y = (y + h / 2) / H
width = w / W
height = h / H

Ultralyticsの物体検出用ラベルは、class、x_center、y_center、width、heightを一行に持ち、座標は画像寸法で正規化した値と説明されています。左上xをそのままWで割って、中心xの欄へ入れないようにします。Ultralytics公式:検出データ形式(新しいタブで開く)

B03なら中心は250、300です。正規化した中心は0.3125、0.5、幅は0.375、高さは0.5になります。

表は横にスクロールできます

IDcenter_xcenter_ywidthheight
B010.1250.1250.250.25
B020.8750.8750.250.25
B030.31250.50.3750.5

元のピクセル座標は残します。出力を丸める場合も、表示桁と計算値を分け、変換先の条件に合わせて再変換や許容誤差を確認してください。

小さな変換関数で、範囲外を混ぜない

次は今回の条件向けのPython例です。クラスの意味や実画像の対象を判断する関数ではありません。

import math


def to_normalized_center(x, y, w, h, image_w, image_h):
    values = (x, y, w, h, image_w, image_h)
    if any(type(v) not in (int, float) or not math.isfinite(v) for v in values):
        raise ValueError("有限の数値が必要です")
    if image_w <= 0 or image_h <= 0:
        raise ValueError("画像寸法が不正です")
    if x < 0 or y < 0 or w <= 0 or h <= 0:
        raise ValueError("位置または幅・高さが不正です")
    if x + w > image_w or y + h > image_h:
        raise ValueError("箱が画像外です")
    return (
        (x + w / 2) / image_w,
        (y + h / 2) / image_h,
        w / image_w,
        h / image_h,
    )

この関数を6件へ適用し、B01〜B03は表の値と一致、B04〜B06は確認対象として拒否されることをローカルで検査しました。実際のアノテーションツール、Torchvision、Ultralyticsへ読み込ませた結果ではありません。

変換を逆にたどれば、幅は正規化幅×W、左上xは正規化中心x×W−幅÷2です。今回の有効3件では、逆変換して元の座標と一致することも確認しました。逆変換が合うことだけで、元の箱が正しい物体を囲んでいるとは分かりません。

ラベルの意味と、座標の計算を分ける

ラベルへ出す場合は、クラス番号と名前の対応も別に用意します。Ultralyticsの説明ではクラス番号は0から始まります。次は「0=example_region」と定めた架空の出力例です。実在の物体を正しく分類した例ではありません。

0 0.125 0.125 0.25 0.25
0 0.875 0.875 0.25 0.25
0 0.3125 0.5 0.375 0.5

このテキストの各行へB01〜B03の対応を別表で残します。数字の並びへ変換した後に、どの元領域から作ったか分からなくならないようにします。実画像とのファイル名対応、形式全体の構成、学習や推論での読み込みは別の確認です。

AIへの依頼では、入力形式、画像IDと版、幅と高さ、出力形式、範囲外の扱いを指定します。「座標をYOLO用にして」だけで、元の座標が左上なのか中心なのかを推測させません。元の画像や寸法が不足する場合は、その行を保留にします。

座標が合うことと、領域が正しいことを分ける

画像内で正の幅高さを持つ箱でも、対象物が入っていない、別の物体を囲む、必要な部分が切れる場合があります。位置の意味は元画像へ重ねて確認します。文字の読取り結果、物体の種類、検出の抜けも、座標の換算だけでは判定できません。

渡す成果物には、元の領域ID、画像寸法、座標形式、元の値、変換後の値、拒否理由、クラス対応を残します。数値検査、ツールへの読み込み、画像上の確認、学習・推論の結果を分けて記録すれば、どの段階でずれたかを追えます。

参考資料

公式仕様の確認日:2026年10月20日。画像寸法と6領域は架空例です。計算、範囲検査、3件の逆変換をローカルで確認しました。実画像、AI検出、アノテーションツールへの取込、学習・推論、画像の重ね合わせ、画面表示は検証していません。

質問から自分に合うAIを探す