#!/usr/bin/env python
# -*- coding: utf-8 -*-
import cv2
import os
import math
import xml.etree.ElementTree as xml
from . import common as com
from .wbia_object import IBEIS_Object
[docs]class IBEIS_Image(object): # NOQA
def __init__(ibsi, filename_xml, absolute_dataset_path, **kwargs):
with open(filename_xml, 'r') as _xml:
_xml = xml.XML(_xml.read().replace('\n', ''))
ibsi.folder = com.get(_xml, 'folder')
ibsi.absolute_dataset_path = absolute_dataset_path
ibsi.filename = com.get(_xml, 'filename')
source = com.get(_xml, 'source', text=False)
ibsi.source_database = com.get(source, 'database')
ibsi.source_annotation = com.get(source, 'annotation')
ibsi.source_image = com.get(source, 'image')
size = com.get(_xml, 'size', text=False)
ibsi.width = int(com.get(size, 'width'))
ibsi.height = int(com.get(size, 'height'))
try:
ibsi.depth = int(com.get(size, 'depth'))
except TypeError:
ibsi.depth = 3
ibsi.segmented = com.get(size, 'segmented') == '1'
ibsi.objects = []
ibsi.objects_patches = []
ibsi.objects_invalid = []
for obj in com.get(_xml, 'object', text=False, singularize=False):
temp = IBEIS_Object(obj, ibsi.width, ibsi.height)
if (
temp.width > kwargs['object_min_width']
and temp.height > kwargs['object_min_height']
):
ibsi.objects.append(temp)
else:
ibsi.objects_invalid.append(temp)
flag = True
for cat in ibsi.categories():
if cat in kwargs['mine_exclude_categories']:
flag = False
if kwargs['mine_negatives'] and flag:
negatives = 0
for i in range(kwargs['mine_max_attempts']):
if negatives >= kwargs['mine_max_keep']:
break
width = com.randInt(
kwargs['mine_width_min'],
min(ibsi.width - 1, kwargs['mine_width_max']),
)
height = com.randInt(
kwargs['mine_height_min'],
min(ibsi.height - 1, kwargs['mine_height_max']),
)
x = com.randInt(0, ibsi.width - width - 1)
y = com.randInt(0, ibsi.height - height - 1)
obj = {
'xmax': x + width,
'xmin': x,
'ymax': y + height,
'ymin': y,
}
overlap_names = ibsi._overlaps(
ibsi.objects, obj, kwargs['mine_overlap_margin']
)
if len(overlap_names) > 0:
continue
ibsi.objects.append(
IBEIS_Object(obj, ibsi.width, ibsi.height, name='MINED')
)
negatives += 1
if kwargs['mine_patches']:
patch_width = kwargs['mine_patch_width']
patch_height = kwargs['mine_patch_height']
x_length = float(ibsi.width - patch_width - 1)
y_length = float(ibsi.height - patch_height - 1)
x_bins = int(x_length / kwargs['mine_patch_stride_suggested'])
y_bins = int(y_length / kwargs['mine_patch_stride_suggested'])
x_bins = max(1, x_bins)
y_bins = max(1, y_bins)
patch_stride_x = x_length / x_bins
patch_stride_y = y_length / y_bins
# ibsi.show()
for x in range(x_bins + 1):
for y in range(y_bins + 1):
x_min = int(x * patch_stride_x)
y_min = int(y * patch_stride_y)
x_max = x_min + patch_width
y_max = y_min + patch_height
assert (
0 <= x_min
and x_max < ibsi.width
and 0 <= y_min
and y_max < ibsi.height
)
# Add patch
obj = {
'xmax': x_max,
'xmin': x_min,
'ymax': y_max,
'ymin': y_min,
}
overlap_names = ibsi._overlaps(
ibsi.objects, obj, kwargs['mine_patch_overlap_margin']
)
if len(overlap_names) > 0:
for overlap_name in overlap_names:
name = '%s' % overlap_name.upper()
ibsi.objects_patches.append(
IBEIS_Object(obj, ibsi.width, ibsi.height, name=name)
)
else:
ibsi.objects_patches.append(
IBEIS_Object(
obj, ibsi.width, ibsi.height, name='NEGATIVE'
)
)
def __str__(ibsi):
return '<IBEIS Image Object | %s | %d objects>' % (
ibsi.filename,
len(ibsi.objects),
)
def __repr__(ibsi):
return '<IBEIS Image Object | %s>' % (ibsi.filename)
def __len__(ibsi):
return len(ibsi.objects)
def __lt__(ibsi1, ibsi2):
if ibsi1.filename < ibsi2.filename:
return -1
if ibsi1.filename < ibsi2.filename:
return 1
return 0
def _distance(pt1, pt2):
(x1, y1) = pt1
(x2, y2) = pt2
return math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
def _overlaps(
ibsi, objects, obj, margin=0.50, bins=['left', 'front', 'right', 'back']
):
names = []
for _obj in objects:
x_overlap = max(0, min(obj['xmax'], _obj.xmax) - max(obj['xmin'], _obj.xmin))
y_overlap = max(0, min(obj['ymax'], _obj.ymax) - max(obj['ymin'], _obj.ymin))
area_overlap = float(x_overlap * y_overlap)
width = obj['xmax'] - obj['xmin']
height = obj['ymax'] - obj['ymin']
area_total = min(width * height, _obj.area)
score = area_overlap / area_total
# print(score)
if score >= margin:
names.append(_obj.name + ':' + _obj.pose_str)
return list(set(names))
[docs] def image_path(ibsi):
return os.path.join(ibsi.absolute_dataset_path, 'JPEGImages', ibsi.filename)
[docs] def categories(ibsi, unique=True, sorted_=True, patches=False):
temp = [_object.name for _object in ibsi.objects]
if patches:
temp += [_object.name for _object in ibsi.objects_patches]
if unique:
temp = list(set(temp))
if sorted_:
temp = sorted(temp)
return temp
[docs] def bounding_boxes(ibsi, **kwargs):
return [_object.bounding_box(**kwargs) for _object in ibsi.objects]
def _accuracy_match(ibsi, prediction, object_list):
# For this non-supressed prediction, compute and assign to the closest bndbox
centerx, centery, minx, miny, maxx, maxy, confidence, supressed = prediction
index_best = None
score_best = -1.0
for index, _object in enumerate(object_list):
width = maxx - minx
height = maxy - miny
x_overlap = max(0, min(maxx, _object.xmax) - max(minx, _object.xmin))
y_overlap = max(0, min(maxy, _object.ymax) - max(miny, _object.ymin))
area_overlap = float(x_overlap * y_overlap)
area_total = (width * height) + _object.area
score = area_overlap / (area_total - area_overlap)
if score >= score_best:
# Wooo! Found a (probably) better candidate, but...
if score == score_best:
# Well, this is awkward?
assert index_best is not None # Just to be sure
_object_best = object_list[index_best]
a = ibsi._distance(
(centerx, centery), (_object_best.xcenter, _object_best.ycenter)
)
b = ibsi._distance(
(centerx, centery), (_object.xcenter, _object.ycenter)
)
if a < b:
# Not a better candidate based on distance
continue
elif a == b:
# First come, first serve
continue
# Save new best
score_best = score
index_best = index
return index_best, score_best
[docs] def accuracy(ibsi, prediction_list, category, alpha=0.5):
# PASCAL ACCURACY MEASUREMENT
object_list = []
for _object in ibsi.objects + ibsi.objects_invalid:
if _object.name == category:
object_list.append(_object)
# Trivial case
if len(object_list) == 0 and len(prediction_list) == 0:
return 1.0, 0.0, 0.0, 0.0
true_positive = 0
false_positive = 0
counters = [0] * len(object_list)
for prediction in prediction_list:
centerx, centery, minx, miny, maxx, maxy, confidence, supressed = prediction
if supressed == 0.0:
index_best, score_best = ibsi._accuracy_match(prediction, object_list)
if score_best >= alpha:
counters[index_best] += 1
true_positive += 1
else:
false_positive += 1
false_negative = counters.count(0)
precision = float(true_positive)
recall = true_positive + false_positive + false_negative
assert recall != 0
return precision / recall, true_positive, false_positive, false_negative
[docs] def show(
ibsi,
objects=True,
parts=True,
display=True,
prediction_list=None,
category=None,
alpha=0.5,
label=True,
):
def _draw_box(
img, annotation, xmin, ymin, xmax, ymax, color, stroke=2, position='top'
):
font = cv2.FONT_HERSHEY_SIMPLEX
scale = 0.5
width, height = cv2.getTextSize(annotation, font, scale, -1)[0]
cv2.rectangle(img, (xmin, ymin), (xmax, ymax), color, stroke)
if label:
if position in ['top']:
cv2.rectangle(
img, (xmin, ymin), (xmin + width, ymin + height), color, -1
)
cv2.putText(
img,
annotation,
(xmin + 5, ymin + height),
font,
0.4,
(255, 255, 255),
)
elif position in ['bottom']:
cv2.rectangle(
img, (xmin, ymax - height), (xmin + width, ymax), color, -1
)
cv2.putText(
img, annotation, (xmin + 5, ymax), font, 0.4, (255, 255, 255)
)
original = com.openImage(ibsi.image_path(), color=True)
color_dict = {}
for _object in ibsi.objects:
color = com.randColor()
color_dict[_object] = color
_draw_box(
original,
_object.name.upper(),
_object.xmin,
_object.ymin,
_object.xmax,
_object.ymax,
color,
)
if parts:
for part in _object.parts:
_draw_box(
original,
part.name.upper(),
part.xmin,
part.ymin,
part.xmax,
part.ymax,
color,
)
for _object in ibsi.objects_invalid:
color = [0, 0, 0]
color_dict[_object] = color
_draw_box(
original,
_object.name.upper(),
_object.xmin,
_object.ymin,
_object.xmax,
_object.ymax,
color,
)
if parts:
for part in _object.parts:
_draw_box(
original,
part.name.upper(),
part.xmin,
part.ymin,
part.xmax,
part.ymax,
color,
)
for _object in ibsi.objects_patches:
if _object.name.upper() == 'NEGATIVE':
continue
color = [255, 0, 0]
else:
color = [0, 0, 255]
color_dict[_object] = color
_draw_box(
original,
_object.name.upper(),
_object.xmin,
_object.ymin,
_object.xmax,
_object.ymax,
color,
)
if parts:
for part in _object.parts:
_draw_box(
original,
part.name.upper(),
part.xmin,
part.ymin,
part.xmax,
part.ymax,
color,
)
if prediction_list is not None:
assert category is not None
object_list = []
for _object in ibsi.objects + ibsi.objects_invalid:
if _object.name == category:
object_list.append(_object)
for prediction in prediction_list:
(
centerx,
centery,
minx,
miny,
maxx,
maxy,
confidence,
supressed,
) = prediction
if supressed == 0.0:
if len(object_list) > 0:
index_best, score_best = ibsi._accuracy_match(
prediction, object_list
)
_object_best = object_list[index_best]
color = color_dict[_object_best]
if score_best >= alpha:
annotation = 'DETECT [TRUE POS %.2f]' % score_best
else:
annotation = 'DETECT [FALSE POS %.2f]' % score_best
cv2.line(
original,
(int(minx), int(miny)),
(_object_best.xmin, _object_best.ymin),
color,
1,
)
cv2.line(
original,
(int(minx), int(maxy)),
(_object_best.xmin, _object_best.ymax),
color,
1,
)
cv2.line(
original,
(int(maxx), int(miny)),
(_object_best.xmax, _object_best.ymin),
color,
1,
)
cv2.line(
original,
(int(maxx), int(maxy)),
(_object_best.xmax, _object_best.ymax),
color,
1,
)
else:
annotation = 'DETECT [FALSE POS]'
color = [0, 0, 255]
_draw_box(
original,
annotation,
int(minx),
int(miny),
int(maxx),
int(maxy),
color,
stroke=1,
position=False,
)
if display:
cv2.imshow(ibsi.filename + ' with Bounding Boxes', original)
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
return original