diff --git a/python/_group_rectangles_with_aux.pyx b/python/_group_rectangles_with_aux.pyx new file mode 100644 index 00000000000..13c655087bf --- /dev/null +++ b/python/_group_rectangles_with_aux.pyx @@ -0,0 +1,38 @@ +""" Example of wrapping a C function that takes C float arrays as input using + the Numpy declarations from Cython """ + +# import both numpy and the Cython declarations for numpy +import numpy as np +cimport numpy as np +cimport cython + +np.import_array() + +# cdefine the signature of our c function +cdef extern from "group_rectangles_with_aux.h": + int groupRectanglesWithAux(float* input, int group_threshold, float eps, \ + float* output, int* scores, int nrows, int ncols) + +def execute(list linput, int group_threshold, float eps = 0.2): + cdef int nrows = len(linput) + cdef int ncols = np.PyArray_DIM(linput[0], 0) + cdef np.ndarray[float, ndim=2, mode="c"] input = np.empty((nrows, ncols), dtype=np.float32) + for i in range(nrows): + input[i, :] = linput[i] + cdef np.ndarray[float, ndim=2, mode="c"] output = np.empty((nrows, ncols), dtype=np.float32) + cdef np.ndarray[np.int32_t, ndim=2, mode="c"] scores = np.empty((nrows, 1), dtype=np.int32) + + cdef int num_output = groupRectanglesWithAux( + np.PyArray_DATA(input), + group_threshold, + eps, + np.PyArray_DATA(output), + np.PyArray_DATA(scores), + nrows, + ncols) + + if ncols > 4: + aux = output[:num_output, 4:] + else: + aux = None + return (np.rint(output[:num_output, :4]).astype(np.int32), aux, scores[:num_output]) diff --git a/python/group_rectangles_setup.py b/python/group_rectangles_setup.py new file mode 100644 index 00000000000..82b9b20ff28 --- /dev/null +++ b/python/group_rectangles_setup.py @@ -0,0 +1,7 @@ +from distutils.core import setup, Extension + +# define the extension module +gr_module = Extension('group_rect_module', sources=['groupRectangles.cpp']) + +# run the setup +setup(ext_modules=[gr_module]) diff --git a/python/group_rectangles_with_aux.cpp b/python/group_rectangles_with_aux.cpp new file mode 100644 index 00000000000..50bae407bff --- /dev/null +++ b/python/group_rectangles_with_aux.cpp @@ -0,0 +1,102 @@ +#include +#include + +#include + +using namespace std; + +using cv::Rect; + +class SimilarRects { +public: + SimilarRects(double _eps) : eps(_eps) {} + inline bool operator() (const vector& r1, const vector& r2) const { + double delta = eps * (std::min(r1[2], r2[2]) + std::min(r1[3], r2[3])) * 0.5; + return std::abs(r1[0] - r2[0]) <= delta && + std::abs(r1[1] - r2[1]) <= delta && + std::abs(r1[0] + r1[2] - r2[0] - r2[2]) <= delta && + std::abs(r1[1] + r1[3] - r2[1] - r2[3]) <= delta; + } + double eps; +}; + +int groupRectanglesWithAux(float* input, int groupThreshold, float eps, + float* output, int* scores, int nrows, int ncols) { + if (groupThreshold <= 0 || nrows == 0) { + return 0; + } + + vector > rectList(nrows, vector(ncols)); + for (int i = 0; i < nrows; ++i) { + int base_index = i * ncols; + for (int j = 0; j < ncols; ++j) { + if (j < 4) { + rectList[i][j] = cv::saturate_cast(input[base_index + j]); + } else { + rectList[i][j] = input[base_index + j]; + } + } + } + + vector labels; + int nclasses = cv::partition(rectList, labels, SimilarRects(eps)); + vector > frects(nclasses, vector(ncols)); + vector rweights(nclasses, 0); + int nlabels = (int) labels.size(); + for (int i = 0; i < nlabels; i++) { + int cls = labels[i]; + for (int j = 0; j < ncols; ++j) { + frects[cls][j] += rectList[i][j]; + } + rweights[cls]++; + } + + for (int i = 0; i < nclasses; i++) { + float s = 1.f / rweights[i]; + for (int j = 0; j < ncols; ++j) { + float scaled = frects[i][j] * s; + if (j < 4) { + frects[i][j] = cv::saturate_cast(scaled); + } else { + frects[i][j] = scaled; + } + } + } + + int j; + int num_output = 0; + for (int i = 0; i < nclasses; i++) { + int r1 = i; + int n1 = rweights[i]; + if (n1 <= groupThreshold) + continue; + // filter out small face rectangles inside large rectangles + for (j = 0; j < nclasses; j++) { + int n2 = rweights[j]; + + if (j == i || n2 <= groupThreshold) + continue; + int r2 = j; + int dx = cv::saturate_cast(frects[r2][2] * eps); + int dy = cv::saturate_cast(frects[r2][3] * eps); + + if (i != j && + frects[r1][0] >= frects[r2][0] - dx && + frects[r1][1] >= frects[r2][1] - dy && + frects[r1][0] + frects[r1][2] <= frects[r2][0] + frects[r2][2] + dx && + frects[r1][1] + frects[r1][3] <= frects[r2][1] + frects[r2][3] + dy && + (n2 > std::max(3, n1) || n1 < 3)) + break; + } + + if (j == nclasses) { + for (int k = 0; k < ncols; ++k) { + output[num_output * ncols + k] = frects[r1][k]; + } + scores[num_output] = n1; + num_output++; + } + } + + return num_output; +} diff --git a/python/group_rectangles_with_aux.h b/python/group_rectangles_with_aux.h new file mode 100644 index 00000000000..f6044841ac3 --- /dev/null +++ b/python/group_rectangles_with_aux.h @@ -0,0 +1,3 @@ +int groupRectanglesWithAux(float* input, int group_threshold, float eps, + float* output, int* scores, int nrows, int ncols); + diff --git a/python/setup_group_rectangles_with_aux.py b/python/setup_group_rectangles_with_aux.py new file mode 100644 index 00000000000..010e980f619 --- /dev/null +++ b/python/setup_group_rectangles_with_aux.py @@ -0,0 +1,24 @@ +from distutils.core import setup +from distutils.extension import Extension +from Cython.Distutils import build_ext +from Cython.Build import cythonize +import numpy +import subprocess + +process = subprocess.Popen( + ['pkg-config', '--libs', 'opencv'], + stdout=subprocess.PIPE) +out, err = process.communicate() +libs = [lib for lib in str(out).split() if '.' in lib] +opencv_path = set(['/'.join(lib.split('/')[:-2]) for lib in libs]).pop() +print 'Your opencv path:', opencv_path + +setup( + cmdclass = {'build_ext': build_ext}, + ext_modules = [Extension( + "group_rectangles_with_aux", + language = "c++", + sources = ["_group_rectangles_with_aux.pyx", "group_rectangles_with_aux.cpp"], + include_dirs=[numpy.get_include(), opencv_path + '/include'], + extra_link_args=libs)] +) diff --git a/python/test_group_rect.py b/python/test_group_rect.py new file mode 100644 index 00000000000..10fb22ed340 --- /dev/null +++ b/python/test_group_rect.py @@ -0,0 +1,56 @@ +import numpy as np +import cv2 +import group_rectangles_with_aux +import random + +num_trials = 10000 +num_rectangles = 500 +arena_size = 100.0 +square_size = 10.0 +gt = 1 +eps = 0.8 +use_aux = True + +for t in range(num_trials): + opencv_gr = [] + my_gr = [] + for r in range(num_rectangles): + e = np.array([ + random.random() * arena_size, random.random() * arena_size, + random.random() * square_size, random.random() * square_size]) + ec = np.concatenate([e, np.array([random.random(), random.random()])]) + opencv_gr.append(e) + my_gr.append(ec if use_aux else e) + + output1, scores1 = cv2.groupRectangles(opencv_gr, gt, eps) + output2, aux2, scores2 = group_rectangles_with_aux.execute(my_gr, gt, eps) + if len(output1) == 0: + assert len(output2) == 0 + else: + try: + assert (output1 == output2).all() + except: + print 'opencv' + print output1 + print 'mine' + print output2 + print 'diff' + print output1.shape, output2.shape + print output1 - output2 + raise + if len(scores1) == 0: + assert len(scores2) == 0 + else: + try: + assert (scores1 == scores2).all() + except: + print 'opencv' + print scores1 + print 'mine' + print scores2 + print 'diff' + print scores1.shape, scores2.shape + print scores1 - scores2 + raise + if t % (num_trials / 20) == 0: + print 'passed', t