Spaces:
Runtime error
Runtime error
File size: 18,689 Bytes
2366e36 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 |
# Copyright (c) OpenMMLab. All rights reserved.
import numpy as np
from shapely.geometry import Polygon as plg
import mmocr.utils as utils
def ignore_pred(pred_boxes, gt_ignored_index, gt_polys, precision_thr):
"""Ignore the predicted box if it hits any ignored ground truth.
Args:
pred_boxes (list[ndarray or list]): The predicted boxes of one image.
gt_ignored_index (list[int]): The ignored ground truth index list.
gt_polys (list[Polygon]): The polygon list of one image.
precision_thr (float): The precision threshold.
Returns:
pred_polys (list[Polygon]): The predicted polygon list.
pred_points (list[list]): The predicted box list represented
by point sequences.
pred_ignored_index (list[int]): The ignored text index list.
"""
assert isinstance(pred_boxes, list)
assert isinstance(gt_ignored_index, list)
assert isinstance(gt_polys, list)
assert 0 <= precision_thr <= 1
pred_polys = []
pred_points = []
pred_ignored_index = []
gt_ignored_num = len(gt_ignored_index)
# get detection polygons
for box_id, box in enumerate(pred_boxes):
poly = points2polygon(box)
pred_polys.append(poly)
pred_points.append(box)
if gt_ignored_num < 1:
continue
# ignore the current detection box
# if its overlap with any ignored gt > precision_thr
for ignored_box_id in gt_ignored_index:
ignored_box = gt_polys[ignored_box_id]
inter_area = poly_intersection(poly, ignored_box)
area = poly.area
precision = 0 if area == 0 else inter_area / area
if precision > precision_thr:
pred_ignored_index.append(box_id)
break
return pred_polys, pred_points, pred_ignored_index
def compute_hmean(accum_hit_recall, accum_hit_prec, gt_num, pred_num):
"""Compute hmean given hit number, ground truth number and prediction
number.
Args:
accum_hit_recall (int|float): Accumulated hits for computing recall.
accum_hit_prec (int|float): Accumulated hits for computing precision.
gt_num (int): Ground truth number.
pred_num (int): Prediction number.
Returns:
recall (float): The recall value.
precision (float): The precision value.
hmean (float): The hmean value.
"""
assert isinstance(accum_hit_recall, (float, int))
assert isinstance(accum_hit_prec, (float, int))
assert isinstance(gt_num, int)
assert isinstance(pred_num, int)
assert accum_hit_recall >= 0.0
assert accum_hit_prec >= 0.0
assert gt_num >= 0.0
assert pred_num >= 0.0
if gt_num == 0:
recall = 1.0
precision = 0.0 if pred_num > 0 else 1.0
else:
recall = float(accum_hit_recall) / gt_num
precision = 0.0 if pred_num == 0 else float(accum_hit_prec) / pred_num
denom = recall + precision
hmean = 0.0 if denom == 0 else (2.0 * precision * recall / denom)
return recall, precision, hmean
def box2polygon(box):
"""Convert box to polygon.
Args:
box (ndarray or list): A ndarray or a list of shape (4)
that indicates 2 points.
Returns:
polygon (Polygon): A polygon object.
"""
if isinstance(box, list):
box = np.array(box)
assert isinstance(box, np.ndarray)
assert box.size == 4
boundary = np.array(
[box[0], box[1], box[2], box[1], box[2], box[3], box[0], box[3]])
point_mat = boundary.reshape([-1, 2])
return plg(point_mat)
def points2polygon(points):
"""Convert k points to 1 polygon.
Args:
points (ndarray or list): A ndarray or a list of shape (2k)
that indicates k points.
Returns:
polygon (Polygon): A polygon object.
"""
if isinstance(points, list):
points = np.array(points)
assert isinstance(points, np.ndarray)
assert (points.size % 2 == 0) and (points.size >= 8)
point_mat = points.reshape([-1, 2])
return plg(point_mat)
def poly_make_valid(poly):
"""Convert a potentially invalid polygon to a valid one by eliminating
self-crossing or self-touching parts.
Args:
poly (Polygon): A polygon needed to be converted.
Returns:
A valid polygon.
"""
return poly if poly.is_valid else poly.buffer(0)
def poly_intersection(poly_det, poly_gt, invalid_ret=None, return_poly=False):
"""Calculate the intersection area between two polygon.
Args:
poly_det (Polygon): A polygon predicted by detector.
poly_gt (Polygon): A gt polygon.
invalid_ret (None|float|int): The return value when the invalid polygon
exists. If it is not specified, the function allows the computation
to proceed with invalid polygons by cleaning the their
self-touching or self-crossing parts.
return_poly (bool): Whether to return the polygon of the intersection
area.
Returns:
intersection_area (float): The intersection area between two polygons.
poly_obj (Polygon, optional): The Polygon object of the intersection
area. Set as `None` if the input is invalid.
"""
assert isinstance(poly_det, plg)
assert isinstance(poly_gt, plg)
assert invalid_ret is None or isinstance(invalid_ret, float) or \
isinstance(invalid_ret, int)
if invalid_ret is None:
poly_det = poly_make_valid(poly_det)
poly_gt = poly_make_valid(poly_gt)
poly_obj = None
area = invalid_ret
if poly_det.is_valid and poly_gt.is_valid:
poly_obj = poly_det.intersection(poly_gt)
area = poly_obj.area
return (area, poly_obj) if return_poly else area
def poly_union(poly_det, poly_gt, invalid_ret=None, return_poly=False):
"""Calculate the union area between two polygon.
Args:
poly_det (Polygon): A polygon predicted by detector.
poly_gt (Polygon): A gt polygon.
invalid_ret (None|float|int): The return value when the invalid polygon
exists. If it is not specified, the function allows the computation
to proceed with invalid polygons by cleaning the their
self-touching or self-crossing parts.
return_poly (bool): Whether to return the polygon of the intersection
area.
Returns:
union_area (float): The union area between two polygons.
poly_obj (Polygon|MultiPolygon, optional): The Polygon or MultiPolygon
object of the union of the inputs. The type of object depends on
whether they intersect or not. Set as `None` if the input is
invalid.
"""
assert isinstance(poly_det, plg)
assert isinstance(poly_gt, plg)
assert invalid_ret is None or isinstance(invalid_ret, float) or \
isinstance(invalid_ret, int)
if invalid_ret is None:
poly_det = poly_make_valid(poly_det)
poly_gt = poly_make_valid(poly_gt)
poly_obj = None
area = invalid_ret
if poly_det.is_valid and poly_gt.is_valid:
poly_obj = poly_det.union(poly_gt)
area = poly_obj.area
return (area, poly_obj) if return_poly else area
def boundary_iou(src, target, zero_division=0):
"""Calculate the IOU between two boundaries.
Args:
src (list): Source boundary.
target (list): Target boundary.
zero_division (int|float): The return value when invalid
boundary exists.
Returns:
iou (float): The iou between two boundaries.
"""
assert utils.valid_boundary(src, False)
assert utils.valid_boundary(target, False)
src_poly = points2polygon(src)
target_poly = points2polygon(target)
return poly_iou(src_poly, target_poly, zero_division=zero_division)
def poly_iou(poly_det, poly_gt, zero_division=0):
"""Calculate the IOU between two polygons.
Args:
poly_det (Polygon): A polygon predicted by detector.
poly_gt (Polygon): A gt polygon.
zero_division (int|float): The return value when invalid
polygon exists.
Returns:
iou (float): The IOU between two polygons.
"""
assert isinstance(poly_det, plg)
assert isinstance(poly_gt, plg)
area_inters = poly_intersection(poly_det, poly_gt)
area_union = poly_union(poly_det, poly_gt)
return area_inters / area_union if area_union != 0 else zero_division
def one2one_match_ic13(gt_id, det_id, recall_mat, precision_mat, recall_thr,
precision_thr):
"""One-to-One match gt and det with icdar2013 standards.
Args:
gt_id (int): The ground truth id index.
det_id (int): The detection result id index.
recall_mat (ndarray): `gt_num x det_num` matrix with element (i,j)
being the recall ratio of gt i to det j.
precision_mat (ndarray): `gt_num x det_num` matrix with element (i,j)
being the precision ratio of gt i to det j.
recall_thr (float): The recall threshold.
precision_thr (float): The precision threshold.
Returns:
True|False: Whether the gt and det are matched.
"""
assert isinstance(gt_id, int)
assert isinstance(det_id, int)
assert isinstance(recall_mat, np.ndarray)
assert isinstance(precision_mat, np.ndarray)
assert 0 <= recall_thr <= 1
assert 0 <= precision_thr <= 1
cont = 0
for i in range(recall_mat.shape[1]):
if recall_mat[gt_id,
i] > recall_thr and precision_mat[gt_id,
i] > precision_thr:
cont += 1
if cont != 1:
return False
cont = 0
for i in range(recall_mat.shape[0]):
if recall_mat[i, det_id] > recall_thr and precision_mat[
i, det_id] > precision_thr:
cont += 1
if cont != 1:
return False
if recall_mat[gt_id, det_id] > recall_thr and precision_mat[
gt_id, det_id] > precision_thr:
return True
return False
def one2many_match_ic13(gt_id, recall_mat, precision_mat, recall_thr,
precision_thr, gt_match_flag, det_match_flag,
det_ignored_index):
"""One-to-Many match gt and detections with icdar2013 standards.
Args:
gt_id (int): gt index.
recall_mat (ndarray): `gt_num x det_num` matrix with element (i,j)
being the recall ratio of gt i to det j.
precision_mat (ndarray): `gt_num x det_num` matrix with element (i,j)
being the precision ratio of gt i to det j.
recall_thr (float): The recall threshold.
precision_thr (float): The precision threshold.
gt_match_flag (ndarray): An array indicates each gt matched already.
det_match_flag (ndarray): An array indicates each box has been
matched already or not.
det_ignored_index (list): A list indicates each detection box can be
ignored or not.
Returns:
tuple (True|False, list): The first indicates the gt is matched or not;
the second is the matched detection ids.
"""
assert isinstance(gt_id, int)
assert isinstance(recall_mat, np.ndarray)
assert isinstance(precision_mat, np.ndarray)
assert 0 <= recall_thr <= 1
assert 0 <= precision_thr <= 1
assert isinstance(gt_match_flag, list)
assert isinstance(det_match_flag, list)
assert isinstance(det_ignored_index, list)
many_sum = 0.
det_ids = []
for det_id in range(recall_mat.shape[1]):
if gt_match_flag[gt_id] == 0 and det_match_flag[
det_id] == 0 and det_id not in det_ignored_index:
if precision_mat[gt_id, det_id] >= precision_thr:
many_sum += recall_mat[gt_id, det_id]
det_ids.append(det_id)
if many_sum >= recall_thr:
return True, det_ids
return False, []
def many2one_match_ic13(det_id, recall_mat, precision_mat, recall_thr,
precision_thr, gt_match_flag, det_match_flag,
gt_ignored_index):
"""Many-to-One match gt and detections with icdar2013 standards.
Args:
det_id (int): Detection index.
recall_mat (ndarray): `gt_num x det_num` matrix with element (i,j)
being the recall ratio of gt i to det j.
precision_mat (ndarray): `gt_num x det_num` matrix with element (i,j)
being the precision ratio of gt i to det j.
recall_thr (float): The recall threshold.
precision_thr (float): The precision threshold.
gt_match_flag (ndarray): An array indicates each gt has been matched
already.
det_match_flag (ndarray): An array indicates each detection box has
been matched already or not.
gt_ignored_index (list): A list indicates each gt box can be ignored
or not.
Returns:
tuple (True|False, list): The first indicates the detection is matched
or not; the second is the matched gt ids.
"""
assert isinstance(det_id, int)
assert isinstance(recall_mat, np.ndarray)
assert isinstance(precision_mat, np.ndarray)
assert 0 <= recall_thr <= 1
assert 0 <= precision_thr <= 1
assert isinstance(gt_match_flag, list)
assert isinstance(det_match_flag, list)
assert isinstance(gt_ignored_index, list)
many_sum = 0.
gt_ids = []
for gt_id in range(recall_mat.shape[0]):
if gt_match_flag[gt_id] == 0 and det_match_flag[
det_id] == 0 and gt_id not in gt_ignored_index:
if recall_mat[gt_id, det_id] >= recall_thr:
many_sum += precision_mat[gt_id, det_id]
gt_ids.append(gt_id)
if many_sum >= precision_thr:
return True, gt_ids
return False, []
def points_center(points):
assert isinstance(points, np.ndarray)
assert points.size % 2 == 0
points = points.reshape([-1, 2])
return np.mean(points, axis=0)
def point_distance(p1, p2):
assert isinstance(p1, np.ndarray)
assert isinstance(p2, np.ndarray)
assert p1.size == 2
assert p2.size == 2
dist = np.square(p2 - p1)
dist = np.sum(dist)
dist = np.sqrt(dist)
return dist
def box_center_distance(b1, b2):
assert isinstance(b1, np.ndarray)
assert isinstance(b2, np.ndarray)
return point_distance(points_center(b1), points_center(b2))
def box_diag(box):
assert isinstance(box, np.ndarray)
assert box.size == 8
return point_distance(box[0:2], box[4:6])
def filter_2dlist_result(results, scores, score_thr):
"""Find out detected results whose score > score_thr.
Args:
results (list[list[float]]): The result list.
score (list): The score list.
score_thr (float): The score threshold.
Returns:
valid_results (list[list[float]]): The valid results.
valid_score (list[float]): The scores which correspond to the valid
results.
"""
assert isinstance(results, list)
assert len(results) == len(scores)
assert isinstance(score_thr, float)
assert 0 <= score_thr <= 1
inds = np.array(scores) > score_thr
valid_results = [results[idx] for idx in np.where(inds)[0].tolist()]
valid_scores = [scores[idx] for idx in np.where(inds)[0].tolist()]
return valid_results, valid_scores
def filter_result(results, scores, score_thr):
"""Find out detected results whose score > score_thr.
Args:
results (ndarray): The results matrix of shape (n, k).
score (ndarray): The score vector of shape (n,).
score_thr (float): The score threshold.
Returns:
valid_results (ndarray): The valid results of shape (m,k) with m<=n.
valid_score (ndarray): The scores which correspond to the
valid results.
"""
assert results.ndim == 2
assert scores.shape[0] == results.shape[0]
assert isinstance(score_thr, float)
assert 0 <= score_thr <= 1
inds = scores > score_thr
valid_results = results[inds, :]
valid_scores = scores[inds]
return valid_results, valid_scores
def select_top_boundary(boundaries_list, scores_list, score_thr):
"""Select poly boundaries with scores >= score_thr.
Args:
boundaries_list (list[list[list[float]]]): List of boundaries.
The 1st, 2nd, and 3rd indices are for image, text and
vertice, respectively.
scores_list (list(list[float])): List of lists of scores.
score_thr (float): The score threshold to filter out bboxes.
Returns:
selected_bboxes (list[list[list[float]]]): List of boundaries.
The 1st, 2nd, and 3rd indices are for image, text and vertice,
respectively.
"""
assert isinstance(boundaries_list, list)
assert isinstance(scores_list, list)
assert isinstance(score_thr, float)
assert len(boundaries_list) == len(scores_list)
assert 0 <= score_thr <= 1
selected_boundaries = []
for boundary, scores in zip(boundaries_list, scores_list):
if len(scores) > 0:
assert len(scores) == len(boundary)
inds = [
iter for iter in range(len(scores))
if scores[iter] >= score_thr
]
selected_boundaries.append([boundary[i] for i in inds])
else:
selected_boundaries.append(boundary)
return selected_boundaries
def select_bboxes_via_score(bboxes_list, scores_list, score_thr):
"""Select bboxes with scores >= score_thr.
Args:
bboxes_list (list[ndarray]): List of bboxes. Each element is ndarray of
shape (n,8)
scores_list (list(list[float])): List of lists of scores.
score_thr (float): The score threshold to filter out bboxes.
Returns:
selected_bboxes (list[ndarray]): List of bboxes. Each element is
ndarray of shape (m,8) with m<=n.
"""
assert isinstance(bboxes_list, list)
assert isinstance(scores_list, list)
assert isinstance(score_thr, float)
assert len(bboxes_list) == len(scores_list)
assert 0 <= score_thr <= 1
selected_bboxes = []
for bboxes, scores in zip(bboxes_list, scores_list):
if len(scores) > 0:
assert len(scores) == bboxes.shape[0]
inds = [
iter for iter in range(len(scores))
if scores[iter] >= score_thr
]
selected_bboxes.append(bboxes[inds, :])
else:
selected_bboxes.append(bboxes)
return selected_bboxes
|