|
|
|
|
|
from collections import namedtuple |
|
import numpy as np |
|
from shapely.geometry import Polygon |
|
""" |
|
reference from : |
|
https://github.com/MhLiao/DB/blob/3c32b808d4412680310d3d28eeb6a2d5bf1566c5/concern/icdar2015_eval/detection/iou.py#L8 |
|
""" |
|
|
|
|
|
class DetectionIoUEvaluator(object): |
|
def __init__(self, iou_constraint=0.5, area_precision_constraint=0.5): |
|
self.iou_constraint = iou_constraint |
|
self.area_precision_constraint = area_precision_constraint |
|
|
|
def evaluate_image(self, gt, pred): |
|
def get_union(pD, pG): |
|
return Polygon(pD).union(Polygon(pG)).area |
|
|
|
def get_intersection_over_union(pD, pG): |
|
return get_intersection(pD, pG) / get_union(pD, pG) |
|
|
|
def get_intersection(pD, pG): |
|
return Polygon(pD).intersection(Polygon(pG)).area |
|
|
|
def compute_ap(confList, matchList, numGtCare): |
|
correct = 0 |
|
AP = 0 |
|
if len(confList) > 0: |
|
confList = np.array(confList) |
|
matchList = np.array(matchList) |
|
sorted_ind = np.argsort(-confList) |
|
confList = confList[sorted_ind] |
|
matchList = matchList[sorted_ind] |
|
for n in range(len(confList)): |
|
match = matchList[n] |
|
if match: |
|
correct += 1 |
|
AP += float(correct) / (n + 1) |
|
|
|
if numGtCare > 0: |
|
AP /= numGtCare |
|
|
|
return AP |
|
|
|
perSampleMetrics = {} |
|
|
|
matchedSum = 0 |
|
|
|
Rectangle = namedtuple('Rectangle', 'xmin ymin xmax ymax') |
|
|
|
numGlobalCareGt = 0 |
|
numGlobalCareDet = 0 |
|
|
|
arrGlobalConfidences = [] |
|
arrGlobalMatches = [] |
|
|
|
recall = 0 |
|
precision = 0 |
|
hmean = 0 |
|
|
|
detMatched = 0 |
|
|
|
iouMat = np.empty([1, 1]) |
|
|
|
gtPols = [] |
|
detPols = [] |
|
|
|
gtPolPoints = [] |
|
detPolPoints = [] |
|
|
|
|
|
gtDontCarePolsNum = [] |
|
|
|
detDontCarePolsNum = [] |
|
|
|
pairs = [] |
|
detMatchedNums = [] |
|
|
|
arrSampleConfidences = [] |
|
arrSampleMatch = [] |
|
|
|
evaluationLog = "" |
|
|
|
for n in range(len(gt)): |
|
points = gt[n]['points'] |
|
dontCare = gt[n]['ignore'] |
|
if not Polygon(points).is_valid: |
|
continue |
|
|
|
gtPol = points |
|
gtPols.append(gtPol) |
|
gtPolPoints.append(points) |
|
if dontCare: |
|
gtDontCarePolsNum.append(len(gtPols) - 1) |
|
|
|
evaluationLog += "GT polygons: " + str(len(gtPols)) + ( |
|
" (" + str(len(gtDontCarePolsNum)) + " don't care)\n" |
|
if len(gtDontCarePolsNum) > 0 else "\n") |
|
|
|
for n in range(len(pred)): |
|
points = pred[n]['points'] |
|
if not Polygon(points).is_valid: |
|
continue |
|
|
|
detPol = points |
|
detPols.append(detPol) |
|
detPolPoints.append(points) |
|
if len(gtDontCarePolsNum) > 0: |
|
for dontCarePol in gtDontCarePolsNum: |
|
dontCarePol = gtPols[dontCarePol] |
|
intersected_area = get_intersection(dontCarePol, detPol) |
|
pdDimensions = Polygon(detPol).area |
|
precision = 0 if pdDimensions == 0 else intersected_area / pdDimensions |
|
if (precision > self.area_precision_constraint): |
|
detDontCarePolsNum.append(len(detPols) - 1) |
|
break |
|
|
|
evaluationLog += "DET polygons: " + str(len(detPols)) + ( |
|
" (" + str(len(detDontCarePolsNum)) + " don't care)\n" |
|
if len(detDontCarePolsNum) > 0 else "\n") |
|
|
|
if len(gtPols) > 0 and len(detPols) > 0: |
|
|
|
outputShape = [len(gtPols), len(detPols)] |
|
iouMat = np.empty(outputShape) |
|
gtRectMat = np.zeros(len(gtPols), np.int8) |
|
detRectMat = np.zeros(len(detPols), np.int8) |
|
for gtNum in range(len(gtPols)): |
|
for detNum in range(len(detPols)): |
|
pG = gtPols[gtNum] |
|
pD = detPols[detNum] |
|
iouMat[gtNum, detNum] = get_intersection_over_union(pD, pG) |
|
|
|
for gtNum in range(len(gtPols)): |
|
for detNum in range(len(detPols)): |
|
if gtRectMat[gtNum] == 0 and detRectMat[ |
|
detNum] == 0 and gtNum not in gtDontCarePolsNum and detNum not in detDontCarePolsNum: |
|
if iouMat[gtNum, detNum] > self.iou_constraint: |
|
gtRectMat[gtNum] = 1 |
|
detRectMat[detNum] = 1 |
|
detMatched += 1 |
|
pairs.append({'gt': gtNum, 'det': detNum}) |
|
detMatchedNums.append(detNum) |
|
evaluationLog += "Match GT #" + \ |
|
str(gtNum) + " with Det #" + str(detNum) + "\n" |
|
|
|
numGtCare = (len(gtPols) - len(gtDontCarePolsNum)) |
|
numDetCare = (len(detPols) - len(detDontCarePolsNum)) |
|
if numGtCare == 0: |
|
recall = float(1) |
|
precision = float(0) if numDetCare > 0 else float(1) |
|
else: |
|
recall = float(detMatched) / numGtCare |
|
precision = 0 if numDetCare == 0 else float(detMatched) / numDetCare |
|
|
|
hmean = 0 if (precision + recall) == 0 else 2.0 * \ |
|
precision * recall / (precision + recall) |
|
|
|
matchedSum += detMatched |
|
numGlobalCareGt += numGtCare |
|
numGlobalCareDet += numDetCare |
|
|
|
perSampleMetrics = { |
|
'gtCare': numGtCare, |
|
'detCare': numDetCare, |
|
'detMatched': detMatched, |
|
} |
|
return perSampleMetrics |
|
|
|
def combine_results(self, results): |
|
numGlobalCareGt = 0 |
|
numGlobalCareDet = 0 |
|
matchedSum = 0 |
|
for result in results: |
|
numGlobalCareGt += result['gtCare'] |
|
numGlobalCareDet += result['detCare'] |
|
matchedSum += result['detMatched'] |
|
|
|
methodRecall = 0 if numGlobalCareGt == 0 else float( |
|
matchedSum) / numGlobalCareGt |
|
methodPrecision = 0 if numGlobalCareDet == 0 else float( |
|
matchedSum) / numGlobalCareDet |
|
methodHmean = 0 if methodRecall + methodPrecision == 0 else 2 * \ |
|
methodRecall * methodPrecision / ( |
|
methodRecall + methodPrecision) |
|
methodMetrics = { |
|
'precision': methodPrecision, |
|
'recall': methodRecall, |
|
'hmean': methodHmean |
|
} |
|
|
|
return methodMetrics |
|
|
|
|
|
if __name__ == '__main__': |
|
evaluator = DetectionIoUEvaluator() |
|
gts = [[{ |
|
'points': [(0, 0), (1, 0), (1, 1), (0, 1)], |
|
'text': 1234, |
|
'ignore': False, |
|
}, { |
|
'points': [(2, 2), (3, 2), (3, 3), (2, 3)], |
|
'text': 5678, |
|
'ignore': False, |
|
}]] |
|
preds = [[{ |
|
'points': [(0.1, 0.1), (1, 0), (1, 1), (0, 1)], |
|
'text': 123, |
|
'ignore': False, |
|
}]] |
|
results = [] |
|
for gt, pred in zip(gts, preds): |
|
results.append(evaluator.evaluate_image(gt, pred)) |
|
metrics = evaluator.combine_results(results) |
|
print(metrics) |
|
|