如何使用两个pandas数据框计算IOU(重叠)?

3

在物体检测中,IOU(交并比)是介于0和1之间的值,表示在某个图像中绘制的2个框之间重叠的百分比。

为了帮助您理解这是什么,以下是一个说明:

iou

红色框是具有坐标x1(左上角),y1(左下角),x2(右上角),y2(右下角)的真实值。

紫色框是具有坐标x1_predicted,y1_predicted,x2_predicted,y2_predicted的预测值。

黄色阴影正方形是IOU,如果它的值大于一定阈值(按惯例为0.5),则预测评估为True,否则为False。

以下是计算两个框的IOU的代码:

def get_iou(box_true, box_predicted):
    x1, y1, x2, y2 = box_true
    x1p, y1p, x2p, y2p = box_predicted
    if not all([x2 > x1, y2 > y1, x2p > x1p, y2p > y1p]):
        return 0
    far_x = np.min([x2, x2p])
    near_x = np.max([x1, x1p])
    far_y = np.min([y2, y2p])
    near_y = np.max([y1, y1p])
    inter_area = (far_x - near_x + 1) * (far_y - near_y + 1)
    true_box_area = (x2 - x1 + 1) * (y2 - y1 + 1)
    pred_box_area = (x2p - x1p + 1) * (y2p - y1p + 1)
    iou = inter_area / (true_box_area + pred_box_area - inter_area)
    return iou

我有两个csv文件包含预测数据和实际数据,我将它们读入两个pandas DataFrame对象中,并从那里开始。

对于每个图像,我提取特定对象类型(例如:汽车)的检测结果和实际数据,以下是一个示例,描述了一张图像(Beverly_hills1.png)中一个对象(汽车)的情况。

Actual:
              Image Path Object Name  X_min  Y_min  X_max  Y_max
3842  Beverly_hills1.png         Car    760    432    911    550
3843  Beverly_hills1.png         Car    612    427    732    526
3844  Beverly_hills1.png         Car    462    412    597    526
3845  Beverly_hills1.png         Car    371    432    544    568

Detections:
                  image object_name   x1   y1   x2   y2
594  Beverly_hills1.png         Car  612  422  737  539
595  Beverly_hills1.png         Car  383  414  560  583

这是我的比较方式:

def calculate_overlaps(self, detections, actual):
    calculations = []
    detection_groups = detections.groupby('image')
    actual_groups = actual.groupby('Image Path')
    for item1, item2 in zip(actual_groups, detection_groups):
        for detected_index, detected_row in item2[1].iterrows():
            detected_coordinates = detected_row.values[2: 6]
            detected_overlaps = []
            coords = []
            for actual_index, actual_row in item1[1].iterrows():
                actual_coordinates = actual_row.values[4: 8]
                detected_overlaps.append((
                    self.get_iou(actual_coordinates, detected_coordinates)))
                coords.append(actual_coordinates)
            detected_row['max_iou'] = max(detected_overlaps)
            x1, y1, x2, y2 = coords[int(np.argmax(detected_overlaps))]
            for match, value in zip([f'{item}_match'
                                     for item in ['x1', 'y1', 'x2', 'y2']],
                                    [x1, y1, x2, y2]):
                detected_row[match] = value
            calculations.append(detected_row)
    return pd.DataFrame(calculations)

每种对象类型都将运行此操作,这是低效的。
最终结果将如下所示:
                     image object_name    x1  ...  y1_match  x2_match  y2_match
594     Beverly_hills1.png         Car   612  ...       427       732       526
595     Beverly_hills1.png         Car   383  ...       432       544       568
1901   Beverly_hills10.png         Car   785  ...       432       940       578
2015  Beverly_hills101.png         Car   832  ...       483      1240       579
2708  Beverly_hills103.png         Car   376  ...       466      1333       741
...                    ...         ...   ...  ...       ...       ...       ...
618    Beverly_hills93.png         Car   922  ...       406       851       659
625    Beverly_hills93.png         Car  1002  ...       406       851       659
1081   Beverly_hills94.png         Car   398  ...       426       527       559
1745   Beverly_hills95.png         Car  1159  ...       438       470       454
1746   Beverly_hills95.png         Car   765  ...       441       772       474

[584 rows x 14 columns]

如何简化/矢量化这个代码,并消除for循环?可以使用np.where()来完成吗?
1个回答

1
首先,我注意到您的get_iou函数有一个条件:x2 > x1,y2 > y1,x2p > x1p,y2p > y1p。您应该确保这个条件对于两个数据框都成立。
其次,actual具有Image PathObject Name列,而detections具有imageobject_name列。您可能想要将相应的列更改为一个单一的名称。
话虽如此,这是我的merge解决方案:
def area(df,columns):
    '''
    compute the box area 
    @param df: a dataframe
    @param columns: box coordinates (x_min, y_min, x_max, y_max)
    '''
    x1,y1,x2,y2 = [df[col] for col in columns]
    return (x2-x1)*(y2-y1)

# rename the columns
actual = actual.rename(columns={'Image Path':'image', 'Object Name':'object_name'})

# merge on `image` and `object_name`
total_df = (actual.merge(detections, on=['image', 'object_name'])

# compute the intersection
total_df['x_max_common'] = total_df[['X_max','x2']].min(1)
total_df['x_min_common'] = total_df[['X_min','x1']].max(1)
total_df['y_max_common'] = total_df[['Y_max','y2']].min(1)
total_df['y_min_common'] = total_df[['Y_min','y1']].max(1)

# valid intersection
true_intersect = (total_df['x_max_common'] > total_df['x_min_common']) & \
                 (total_df['y_max_common'] > total_df['y_min_common'])

# filter total_df with valid intersection
total_df = total_df[true_intersect]

# the various areas
actual_areas = area(total_df, ['X_min','Y_min','X_max','Y_max'])
predicted_areas = area(total_df, ['x1','y1','x2','y2'])
intersect_areas = area(total_df,['x_min_common','y_min_common','x_max_common', 'y_max_common'])

# IOU
iou_areas = intersect_areas/(actual_areas + predicted_areas - intersect_areas)

# assign the IOU to total_df
total_df['IOU'] = iou_areas

哇,这真是太棒了,谢谢!在16个对象类上的总执行时间仅占3个嵌套for循环的总执行时间的2%[〜18秒-0.335秒]!我真的很喜欢这个。 - user12690225
虽然有一些我不明白的地方,那就是,在计算IOU之后如何取消合并?接下来我想要计算真正的正样本和假正样本,因此我需要单独获取初始预测值,并且在计算后,total_df的长度比初始预测值大。 - user12690225
我正在使用这个来计算每个类别的mAP(平均精度)和检测器的mAP,所以有什么解决方法吗?顺便说一下,如果这有任何区别,我会分别在每个类别的数据(检测/实际)上运行它。 - user12690225
实际上,false_positive = total - true_positive,所以您可以这样做。 - Quang Hoang
1
这也是可能的,但说实话,这超出了评论的范畴,应该单独提出一个问题。 - Quang Hoang
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接