diff --git a/examples/tracking/visual/PyCV_TrackFeaturesFwdBck.jl b/examples/tracking/visual/PyCV_TrackFeaturesFwdBck.jl index 3618b53d0..5e28852b3 100644 --- a/examples/tracking/visual/PyCV_TrackFeaturesFwdBck.jl +++ b/examples/tracking/visual/PyCV_TrackFeaturesFwdBck.jl @@ -12,6 +12,12 @@ using SHA: sha256 np = pyimport("numpy") cv = pyimport("cv2") +pushfirst!(PyVector(pyimport("sys")."path"), @__DIR__ ) + +SscPy = pyimport("PySSCFeatures") +ssc = SscPy."ssc" + + # # lk_params = ( winSize = (19, 19), maxLevel = 2, criteria = (cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 0.03)) # lk_params = ( winSize = (19, 19), maxLevel = 2, criteria = (cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 30, 0.01)) # feature_params = (maxCorners = 1000, qualityLevel = 0.01, minDistance = 8, blockSize = 19 ) @@ -43,16 +49,22 @@ function goodFeaturesToTrack(im1, feature_params; mask=nothing) cv.goodFeaturesToTrack(collect(reinterpret(UInt8, im1)), mask=collect(reinterpret(UInt8,mask)), feature_params...) end -function goodFeaturesToTrackORB(im1; mask=nothing, orb = cv.ORB_create()) +function goodFeaturesToTrackORB(im1; mask=nothing, orb = cv.ORB_create(), downsample::Int=5, tolerance::Real = 0.1) # gray = cv2.cvtColor(im1,cv.COLOR_BGR2GRAY) # kypts, decrs = orb.detectAndCompute(gray,None) # https://docs.opencv.org/3.4/d1/d89/tutorial_py_orb.html # find the keypoints with ORB img = collect(reinterpret(UInt8, im1)) kp = orb.detect(img, collect(reinterpret(UInt8,mask))) + + # downselect a better distribution of features + rows, cols = size(img,1), size(img,2) + sel_kp = ssc(kp, orb.getMaxFeatures() รท downsample, tolerance, cols, rows) + # compute the descriptors with ORB - kp, des = orb.compute(img, kp) - return kp, des + kp_, des = orb.compute(img, sel_kp) + + return kp_, des end function combinePlot(ref_img, overlay_img) diff --git a/examples/tracking/visual/PySSCFeatures.py b/examples/tracking/visual/PySSCFeatures.py new file mode 100644 index 000000000..78f57d52a --- /dev/null +++ b/examples/tracking/visual/PySSCFeatures.py @@ -0,0 +1,131 @@ +# Taken from: https://github.com/BAILOOL/ANMS-Codes/blob/568fa6a1b39aa46fa04ad600cb40a33decf52897/Python/ssc.py#L1 + +# @article{bailo2018efficient, +# title={Efficient adaptive non-maximal suppression algorithms for homogeneous spatial keypoint distribution}, +# author={Bailo, Oleksandr and Rameau, Francois and Joo, Kyungdon and Park, Jinsun and Bogdan, Oleksandr and Kweon, In So}, +# journal={Pattern Recognition Letters}, +# volume={106}, +# pages={53--60}, +# year={2018}, +# publisher={Elsevier} +# } + +# MIT "Expat" License + +# Copyright (c) 2018 Oleksandr Bailo + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +import math + +def ssc(keypoints, num_ret_points, tolerance, cols, rows): + exp1 = rows + cols + 2 * num_ret_points + exp2 = ( + 4 * cols + + 4 * num_ret_points + + 4 * rows * num_ret_points + + rows * rows + + cols * cols + - 2 * rows * cols + + 4 * rows * cols * num_ret_points + ) + exp3 = math.sqrt(exp2) + exp4 = num_ret_points - 1 + + sol1 = -round(float(exp1 + exp3) / exp4) # first solution + sol2 = -round(float(exp1 - exp3) / exp4) # second solution + + high = ( + sol1 if (sol1 > sol2) else sol2 + ) # binary search range initialization with positive solution + low = math.floor(math.sqrt(len(keypoints) / num_ret_points)) + + prev_width = -1 + selected_keypoints = [] + result_list = [] + result = [] + complete = False + k = num_ret_points + k_min = round(k - (k * tolerance)) + k_max = round(k + (k * tolerance)) + + while not complete: + width = low + (high - low) / 2 + if ( + width == prev_width or low > high + ): # needed to reassure the same radius is not repeated again + result_list = result # return the keypoints from the previous iteration + break + + c = width / 2 # initializing Grid + num_cell_cols = int(math.floor(cols / c)) + num_cell_rows = int(math.floor(rows / c)) + covered_vec = [ + [False for _ in range(num_cell_cols + 1)] for _ in range(num_cell_rows + 1) + ] + result = [] + + for i in range(len(keypoints)): + row = int( + math.floor(keypoints[i].pt[1] / c) + ) # get position of the cell current point is located at + col = int(math.floor(keypoints[i].pt[0] / c)) + if not covered_vec[row][col]: # if the cell is not covered + result.append(i) + # get range which current radius is covering + row_min = int( + (row - math.floor(width / c)) + if ((row - math.floor(width / c)) >= 0) + else 0 + ) + row_max = int( + (row + math.floor(width / c)) + if ((row + math.floor(width / c)) <= num_cell_rows) + else num_cell_rows + ) + col_min = int( + (col - math.floor(width / c)) + if ((col - math.floor(width / c)) >= 0) + else 0 + ) + col_max = int( + (col + math.floor(width / c)) + if ((col + math.floor(width / c)) <= num_cell_cols) + else num_cell_cols + ) + for row_to_cover in range(row_min, row_max + 1): + for col_to_cover in range(col_min, col_max + 1): + if not covered_vec[row_to_cover][col_to_cover]: + # cover cells within the square bounding box with width w + covered_vec[row_to_cover][col_to_cover] = True + + if k_min <= len(result) <= k_max: # solution found + result_list = result + complete = True + elif len(result) < k_min: + high = width - 1 # update binary search range + else: + low = width + 1 + prev_width = width + + for i in range(len(result_list)): + selected_keypoints.append(keypoints[result_list[i]]) + + return selected_keypoints