Source code for nms.fast

"""
.. module:: fast
   :synopsis: NMS implementation based on OpenCV NMSFast

.. moduleauthor:: tom hoag <tomhoag@gmail.com>

NMS implementation based on OpenCV NMSFast

The functions in this module are not usually called directly.  Instead use :func:`nms.nms.boxes`,
:func:`nms.nms.rboxes`, or :func:`nms.nms.polygons` and set `nms_algorithm=nms.fast`


"""
import numpy as np
import cv2

import nms.helpers as help


[docs]def rectangle_iou(rectA, rectB): """Computes the ratio of the intersection area of the input rectangles to the (sum of rectangle areas - intersection area). Used with the NMS function. :param rectA: a rectangle described by (x,y,w,h) :type rectA: tuple :param rectB: a rectangle describe by (x,y,w,h) :type rectB: tuple :returns: The ratio of overlap between rectA and rectB (intersection area/(rectA area + rectB area - intersection area) """ rectAd = ((rectA[0], rectA[1]),(rectA[2],rectA[3]), 0) rectBd = ((rectB[0], rectB[1]),(rectB[2], rectB[3]), 0) # rotatedRectangleIntersection expects (rrect, rrect) as ((cx, cy), (w, h), deg) retVal, region = cv2.rotatedRectangleIntersection(rectAd, rectBd) # cv2.rotatedRectangleIntersection -- rectangles passed are rotated around their centers. if cv2.INTERSECT_NONE == retVal: return 0 elif cv2.INTERSECT_FULL == retVal: return 1.0 else: # https://docs.opencv.org/master/d3/dc0/group__imgproc__shape.html#ga2c759ed9f497d4a618048a2f56dc97f1 intersection_area = cv2.contourArea(region) rectA_area = rectA[2] * rectA[3] rectB_area = rectB[2] * rectB[3] return intersection_area / (rectA_area + rectB_area - intersection_area)
[docs]def rotated_rect_iou(rectA, rectB): """Computes the ratio of the intersection area of the input rectangles to the (sum of rectangle areas - intersection area) Used with the NMS function :param rectA: a polygon (rectangle) described by its verticies :type rectA: list :param rectB: a polygon (rectangle) describe by it verticies :type rectB: list :returns: The ratio of the intersection area / (sum of rectangle areas - intersection area) """ return polygon_iou(rectA, rectB)
[docs]def polygon_iou(poly1, poly2, useCV2=True): """Computes the ratio of the intersection area of the input polygons to the (sum of polygon areas - intersection area) Used with the NMS function :param poly1: a polygon described by its verticies :type poly1: list :param poly2: a polygon describe by it verticies :type poly2: list :param useCV2: if True (default), use cv2.contourArea to calculate polygon areas. If false use :func:`nms.helpers.polygon_intersection_area`. :type useCV2: bool :return: The ratio of the intersection area / (sum of rectangle areas - intersection area) :rtype: float """ intersection_area = help.polygon_intersection_area([poly1, poly2]) if intersection_area == 0: return 0 if(useCV2): poly1_area = cv2.contourArea(np.array(poly1, np.int32)) poly2_area = cv2.contourArea(np.array(poly2, np.int32)) else: poly1_area = help.polygon_intersection_area([poly1]) poly2_area = help.polygon_intersection_area([poly2]) return intersection_area / (poly1_area + poly2_area - intersection_area)
[docs]def nms(boxes, scores, **kwargs): """Do Non Maximal Suppression As translated from the OpenCV c++ source in `nms.inl.hpp <https://github.com/opencv/opencv/blob/ee1e1ce377aa61ddea47a6c2114f99951153bb4f/modules/dnn/src/nms.inl.hpp#L67>`__ which was in turn inspired by `Piotr Dollar's NMS implementation in EdgeBox. <https://goo.gl/jV3JYS>`_ This function is not usually called directly. Instead use :func:`nms.nms.boxes`, :func:`nms.nms.rboxes`, or :func:`nms.nms.polygons` :param boxes: the boxes to compare, the structure of the boxes must be compatible with the compare_function. :type boxes: list :param scores: the scores associated with boxes :type scores: list :param kwargs: optional keyword parameters :type kwargs: dict (see below) :returns: an list of indicies of the best boxes :rtype: list :kwargs: * score_threshold (float): the minimum score necessary to be a viable solution, default 0.3 * nms_threshold (float): the minimum nms value to be a viable solution, default: 0.4 * compare_function (function): function that accepts two boxes and returns their overlap ratio, this function must accept two boxes and return an overlap ratio * eta (float): a coefficient in adaptive threshold formula: \ |nmsi1|\ =eta\*\ |nmsi0|\ , default: 1.0 * top_k (int): if >0, keep at most top_k picked indices. default:0 .. |nmsi0| replace:: nms_threshold\ :sub:`i`\ .. |nmsi1| replace:: nms_threshold\ :sub:`(i+1)`\ """ if 'eta' in kwargs: eta = kwargs['eta'] else: eta = 1.0 assert 0 < eta <= 1.0 if 'top_k' in kwargs: top_k = kwargs['top_k'] else: top_k = 0 assert 0 <= top_k if 'score_threshold' in kwargs: score_threshold = kwargs['score_threshold'] else: score_threshold = 0.3 assert score_threshold > 0 if 'nms_threshold' in kwargs: nms_threshold = kwargs['nms_threshold'] else: nms_threshold = 0.4 assert 0 < nms_threshold < 1 if 'compare_function' in kwargs: compare_function = kwargs['compare_function'] else: compare_function = None assert compare_function is not None if len(boxes) == 0: return [] assert len(scores) == len(boxes) assert scores is not None # sort scores descending and convert to [[score], [indexx], . . . ] scores = help.get_max_score_index(scores, score_threshold, top_k) # Do Non Maximal Suppression # This is an interpretation of NMS from the OpenCV source in nms.cpp and nms. adaptive_threshold = nms_threshold indicies = [] for i in range(0, len(scores)): idx = int(scores[i][1]) keep = True for k in range(0, len(indicies)): if not keep: break kept_idx = indicies[k] overlap = compare_function(boxes[idx], boxes[kept_idx]) keep = (overlap <= adaptive_threshold) if keep: indicies.append(idx) if keep and (eta < 1) and (adaptive_threshold > 0.5): adaptive_threshold = adaptive_threshold * eta return indicies