← Назад к вопросам

Как работает IoU (Intersection over Union)?

2.0 Middle🔥 181 комментариев
#Глубокое обучение#Метрики и оценка моделей

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

IoU (Intersection over Union)

IoU (Intersection over Union), также известен как Jaccard Index — это метрика, используемая для оценки качества предсказаний моделей, особенно в компьютерном зрении для задач обнаружения объектов (object detection) и семантической сегментации. IoU измеряет, насколько хорошо предсказанный объект совпадает с истинным объектом.

Математическое определение

Формула

IoU = Area of Intersection / Area of Union
    = |Pred AND True| / |Pred OR True|

Визуально:

Предсказанный bbox (красный)   Истинный bbox (синий)

         Intersection (пересечение)
               /\\
          ____/  \\____
         |___  /\\  ___|  <- Union (объединение)
            |/____\\|

IoU = (жёлтая площадь) / (вся затенённая площадь)

IoU для Bounding Boxes (Object Detection)

Пример расчёта с прямоугольниками

import numpy as np
from matplotlib.patches import Rectangle
import matplotlib.pyplot as plt

def bbox_iou(box1, box2):
    """
    Вычислить IoU для двух bounding box-ов
    box: [x1, y1, x2, y2] — координаты левого верхнего и правого нижнего углов
    """
    # Вычислить координаты пересечения
    xi1 = max(box1[0], box2[0])
    yi1 = max(box1[1], box2[1])
    xi2 = min(box1[2], box2[2])
    yi2 = min(box1[3], box2[3])
    
    # Если нет пересечения, IoU = 0
    if xi2 < xi1 or yi2 < yi1:
        return 0.0
    
    # Площадь пересечения
    intersection_area = (xi2 - xi1) * (yi2 - yi1)
    
    # Площадь каждого бокса
    box1_area = (box1[2] - box1[0]) * (box1[3] - box1[1])
    box2_area = (box2[2] - box2[0]) * (box2[3] - box2[1])
    
    # Площадь объединения (Union)
    union_area = box1_area + box2_area - intersection_area
    
    # IoU
    iou = intersection_area / union_area
    return iou

# Пример
pred_box = [50, 50, 200, 200]  # Предсказание: x1, y1, x2, y2
true_box = [100, 100, 250, 250]  # Истинный бокс

iou = bbox_iou(pred_box, true_box)
print(f'IoU: {iou:.4f}')

# Визуализация
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
ax.add_patch(Rectangle((pred_box[0], pred_box[1]), 
                       pred_box[2] - pred_box[0], 
                       pred_box[3] - pred_box[1],
                       linewidth=2, edgecolor='red', facecolor='none', label='Predicted'))
ax.add_patch(Rectangle((true_box[0], true_box[1]),
                       true_box[2] - true_box[0],
                       true_box[3] - true_box[1],
                       linewidth=2, edgecolor='blue', facecolor='none', label='Ground Truth'))
ax.set_xlim(0, 300)
ax.set_ylim(0, 300)
ax.set_aspect('equal')
ax.legend()
ax.set_title(f'IoU = {iou:.4f}')
plt.show()

Пример расчёта вручную

Предсказанный бокс: [50, 50, 200, 200]
Истинный бокс: [100, 100, 250, 250]

Пересечение:
  x1 = max(50, 100) = 100
  y1 = max(50, 100) = 100
  x2 = min(200, 250) = 200
  y2 = min(200, 250) = 200
  Intersection Area = (200 - 100) * (200 - 100) = 100 * 100 = 10000

Площади:
  Pred Area = (200 - 50) * (200 - 50) = 150 * 150 = 22500
  True Area = (250 - 100) * (250 - 100) = 150 * 150 = 22500
  Union Area = 22500 + 22500 - 10000 = 35000

IoU = 10000 / 35000 = 0.2857

IoU для семантической сегментации (Pixel-level)

Работа с масками пиксельного уровня

import numpy as np
from sklearn.metrics import jaccard_score

def mask_iou(pred_mask, true_mask):
    """
    Вычислить IoU для масок пикселей
    pred_mask: предсказанная маска (2D/3D массив с 0 и 1)
    true_mask: истинная маска
    """
    # Пересечение
    intersection = np.logical_and(pred_mask, true_mask).sum()
    
    # Объединение
    union = np.logical_or(pred_mask, true_mask).sum()
    
    # Если оба массива нулевые
    if union == 0:
        return 1.0 if intersection == 0 else 0.0
    
    return intersection / union

# Пример
pred_mask = np.array([
    [0, 1, 1, 0],
    [1, 1, 1, 0],
    [1, 1, 0, 0],
    [0, 0, 0, 0]
])

true_mask = np.array([
    [0, 1, 1, 1],
    [0, 1, 1, 1],
    [0, 1, 1, 0],
    [0, 0, 0, 0]
])

iou = mask_iou(pred_mask, true_mask)
print(f'IoU: {iou:.4f}')

# Альтернативно: использовать sklearn
iou_sklearn = jaccard_score(true_mask.flatten(), pred_mask.flatten())
print(f'IoU (sklearn): {iou_sklearn:.4f}')

mAP (Mean Average Precision) — использует IoU

В object detection используется метрика mAP, которая основана на IoU с порогом (обычно IoU > 0.5).

def calculate_map(predictions, ground_truths, iou_threshold=0.5):
    """
    predictions: список [box, confidence, class_id]
    ground_truths: список [box, class_id]
    """
    # Сортировать предсказания по confidence
    predictions = sorted(predictions, key=lambda x: x[1], reverse=True)
    
    tp = []  # True Positives
    fp = []  # False Positives
    
    for pred_box, confidence, pred_class in predictions:
        max_iou = 0
        matched_gt = None
        
        # Найти лучший совпадающий ground truth
        for gt_box, gt_class in ground_truths:
            if pred_class != gt_class:
                continue
            
            iou = bbox_iou(pred_box, gt_box)
            if iou > max_iou:
                max_iou = iou
                matched_gt = (gt_box, gt_class)
        
        # Проверить, превышает ли IoU порог
        if max_iou > iou_threshold:
            tp.append(1)
            fp.append(0)
            if matched_gt:
                ground_truths.remove(matched_gt)  # Бокс уже использован
        else:
            tp.append(0)
            fp.append(1)
    
    # Вычислить Precision и Recall
    tp_cumsum = np.cumsum(tp)
    fp_cumsum = np.cumsum(fp)
    
    recall = tp_cumsum / len(ground_truths)
    precision = tp_cumsum / (tp_cumsum + fp_cumsum)
    
    # AP = площадь под P-R кривой
    ap = np.trapz(precision, recall)
    return ap

Интерпретация значений IoU

IoU значение       Интерпретация
─────────────────  ──────────────────────────────────
0.9 - 1.0         Отлично, почти идеальное совпадение
0.7 - 0.9         Хорошо, сильное совпадение
0.5 - 0.7         Приемлемо, разумное совпадение
0.3 - 0.5         Слабо, слабое совпадение
0.0 - 0.3         Плохо, очень слабое совпадение

Сравнение IoU с другими метриками

МетрикаОбласть примененияФормула
IoUObject Detection, SegmentationIntersection / Union
DiceSegmentation2 * Intersection / (Pred + True)
PrecisionClassificationTP / (TP + FP)
RecallClassificationTP / (TP + FN)
mAPObject DetectionСреднее AP по всем классам

Когда использовать какую метрику

# IoU — лучше для object detection
# Потому что штрафует неправильную локализацию

# Dice — альтернатива для segmentation
# Формула Dice аналогична F1-score для пикселей

def dice_coefficient(pred_mask, true_mask):
    intersection = np.logical_and(pred_mask, true_mask).sum()
    return 2 * intersection / (pred_mask.sum() + true_mask.sum())

Практический пример с YOLO или Faster R-CNN

import torch
from torchvision.ops import box_iou

# Bounding boxes в формате [x1, y1, x2, y2]
pred_boxes = torch.tensor([
    [50.0, 50.0, 200.0, 200.0],
    [100.0, 100.0, 300.0, 300.0]
])

true_boxes = torch.tensor([
    [100.0, 100.0, 250.0, 250.0]
])

# Вычислить IoU между всеми парами
iou_matrix = box_iou(pred_boxes, true_boxes)
print(f'IoU Matrix:\n{iou_matrix}')

# Максимальное совпадение для каждого предсказания
max_iou, _ = torch.max(iou_matrix, dim=1)
print(f'Max IoU for each prediction: {max_iou}')

Выводы

  • IoU — универсальная метрика для задач локализации (detection, segmentation)
  • Формула простая: пересечение / объединение
  • Диапазон: от 0 (нет совпадения) до 1 (идеальное совпадение)
  • IoU >= 0.5 — стандартный порог для object detection
  • Лучше использовать IoU, чем просто accuracy для локализационных задач
  • mAP в detection — это среднее precision при разных IoU порогах
Как работает IoU (Intersection over Union)? | PrepBro