From 78f1aec00d614841e3eb51966fb1f4bf4599ef22 Mon Sep 17 00:00:00 2001 From: Jens Ahrensfeld Date: Sun, 30 Jun 2024 21:25:27 +0200 Subject: [PATCH] - refactored into utils.py - added ocv_template_matching.py for evaluationof template matcher - fixed template matching from tracker bounding box --- .gitignore | 1 + ocv_template_matching.py | 49 ++++++++++++++++++++ ocv_track.py | 99 +++++++++------------------------------- template_matcher.py | 33 ++++++++++++++ util.py | 53 +++++++++++++++++++++ 5 files changed, 158 insertions(+), 77 deletions(-) create mode 100644 .gitignore create mode 100644 ocv_template_matching.py create mode 100644 template_matcher.py create mode 100644 util.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/ocv_template_matching.py b/ocv_template_matching.py new file mode 100644 index 0000000..e318c3f --- /dev/null +++ b/ocv_template_matching.py @@ -0,0 +1,49 @@ +import cv2 +import sys +import imutils as im + +from template_matcher import template_match_rotation +from util import bbox_extend, bbox_round, process_image_bbox, image_crop + + +video = cv2.VideoCapture("./data/IMG_1730.MP4") + +# Exit if video not open +# d. +if not video.isOpened(): + print("Could not open video") + sys.exit() + +# Read first frame. +ok, image = video.read() +if not ok: + print('Cannot read video file') + sys.exit() + +bbox_template = cv2.selectROI("Image", image, False) +template = image_crop(image, bbox_extend(bbox_template, 0.5)) +cv2.imshow("Template", template) + +# Perform template match operations +res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED) +(min_val, max_val, min_loc, max_loc) = cv2.minMaxLoc(res) + +h, w = (template.shape[0], template.shape[1]) + +print(f"(w, h): {(w, h)}, max_loc: {max_loc}") + +top_left = max_loc +bottom_right = (top_left[0] + w, top_left[1] + h) + +cv2.rectangle(image, top_left, bottom_right, (0, 255, 0), 2) +cv2.rectangle(image, (bbox_template[0], bbox_template[1]), (bbox_template[0] + bbox_template[2], bbox_template[1] + bbox_template[3]), (0, 0, 255), 2) + + +cv2.imshow("Image", image) +cv2.imshow("Matcher", res) + +while True: + # Exit if ESC pressed + k = cv2.waitKey(1) & 0xff + if k == 27: + break diff --git a/ocv_track.py b/ocv_track.py index aa7f9f6..7d8d1a7 100644 --- a/ocv_track.py +++ b/ocv_track.py @@ -1,75 +1,12 @@ import cv2 import sys -import numpy as np import imutils as im +from template_matcher import template_match_rotation +from util import bbox_extend, bbox_round, process_image_bbox, image_crop, bbox_add_position, to_rect + IMG_SCALE_UP = 1 -TEMPLATE_MATCH_OVERLAP = 0.0 - -(major_ver, minor_ver, subminor_ver) = cv2.__version__.split('.') -print(cv2.__version__) - - -def bbox_round(src): - x = int(round(src[0])) - y = int(round(src[1])) - w = int(round(src[2])) - h = int(round(src[3])) - - return x, y, w, h - - -def bbox_extend(bbox: cv2.typing.Rect, factor: float = 0): - we2 = bbox[2]*factor/2 - he2 = bbox[3]*factor/2 - - return bbox_round((bbox[0]-we2, bbox[1]-he2, bbox[2]+2*we2, bbox[3]+2*he2)) - - -def image_crop(src, bbox: cv2.typing.Rect): - x = bbox[0] - y = bbox[1] - w = bbox[2] - h = bbox[3] - - dst = src[y:y+h, x:x+w] - return dst - - -def process_image_bbox(src): - # convert to gray - gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) - - return gray - - -def template_match_rotation(source, template, angle, center, rot_min=-1.0, rot_max=+1.0, n_steps=10, scale=1): - best_angle = 0 - best_r = 0 - best_max_loc = 0 - r = 0 - im_source = im.resize(source, scale * source.shape[0], scale * source.shape[1]) - for dangle in np.linspace(rot_min, rot_max, n_steps): - # rotated and scale up to IMG_SCALE_UP-size - im_template = im.rotate(template, angle + dangle, center=center, scale=scale) - - # Perform template match operations - res = cv2.matchTemplate(im_source, im_template, cv2.TM_CCOEFF_NORMED) - (minVal, maxVal, minLoc, maxLoc) = cv2.minMaxLoc(res) - - r = maxVal - if r > best_r: - best_r = r - best_angle = dangle - best_max_loc = (maxLoc[0]/scale, maxLoc[1]/scale) - else: - break - - angle += best_angle - loc_x = best_max_loc[0] - loc_y = best_max_loc[1] - - return angle, (loc_x, loc_y) +TEMPLATE_MATCH_OVERLAP = 1.0 def match(source, template, angle, center): @@ -138,8 +75,7 @@ if not ok: print('Cannot read video file') sys.exit() -bbox_init = cv2.selectROI("Frame", frame_init, False) -bbox_template = bbox_extend(bbox_init, TEMPLATE_MATCH_OVERLAP) +bbox_template = cv2.selectROI("Frame", frame_init, False) image_template = process_image_bbox(image_crop(frame_init, bbox_template)) ref = Reference(image_template) ref_center = ref.set() @@ -147,7 +83,7 @@ ref_center = ref.set() print(f"ref_center = {ref_center}") # Initialize tracker with first frame and bounding box tracker.create() -tracker.init(frame_init, bbox_init) +tracker.init(frame_init, bbox_template) current_angle = 0 path = [] @@ -166,10 +102,10 @@ while True: ok, bbox_tracker = tracker.update(frame) image_template_rotated = image_template - bbox = bbox_round(bbox_tracker) + bbox_tracker_ext = bbox_extend(bbox_tracker, TEMPLATE_MATCH_OVERLAP) if ok: - image_bbox = process_image_bbox(image_crop(frame, bbox)) + image_bbox = process_image_bbox(image_crop(frame, bbox_tracker_ext)) current_angle, max_loc = match(image_bbox, image_template, current_angle, ref_center) # determine the starting and ending (x, y)-coordinates of the @@ -178,16 +114,25 @@ while True: print(f"current_angle: {current_angle:.2f}, startX: {startX}, startY: {startY}") image_template_rotated = im.rotate(image_template, current_angle, center=ref_center) - # draw the bounding box on the image - p1 = (int(startX + bbox[0]), int(startY + bbox[1])) - p2 = (int(bbox[0]+bbox[2]), int(bbox[1]+bbox[3])) - cv2.rectangle(frame, p1, p2, (255, 0, 0), 3) + # Matcher + matcher_bbox = (max_loc[0], max_loc[1], image_template.shape[1], image_template.shape[0]) + matcher_bbox_global = bbox_round(bbox_add_position(matcher_bbox, bbox_tracker)) + matcher_top_left, matcher_bottom_right = to_rect(matcher_bbox_global) + cv2.rectangle(frame, matcher_top_left, matcher_bottom_right, (255, 0, 0), 3) + + # Tracker + tracker_top_left, tracker_bottom_right = to_rect(bbox_round(bbox_tracker)) + cv2.rectangle(frame, tracker_top_left, tracker_bottom_right, (0, 0, 255), 3) + + # Tracker ext + tracker_top_left, tracker_bottom_right = to_rect(bbox_round(bbox_tracker_ext)) + cv2.rectangle(frame, tracker_top_left, tracker_bottom_right, (255, 0, 255), 3) # Calculate Frames per second (FPS) fps = cv2.getTickFrequency() / (cv2.getTickCount() - timer) # Draw path - line_to = (round(startX + bbox[0]), round(startY + bbox[1])) + line_to = (round(startX + bbox_tracker[0]), round(startY + bbox_tracker[1])) if line_from is not None: path.append({'from': line_from, 'to': line_to}) line_from = line_to diff --git a/template_matcher.py b/template_matcher.py new file mode 100644 index 0000000..512e88d --- /dev/null +++ b/template_matcher.py @@ -0,0 +1,33 @@ +import cv2 +import sys +import numpy as np +import imutils as im + + +def template_match_rotation(source, template, angle, center, rot_min=-1.0, rot_max=+1.0, n_steps=10, scale=1): + best_angle = 0 + best_r = 0 + best_max_loc = 0 + r = 0 + im_source = im.resize(source, scale * source.shape[0], scale * source.shape[1]) + for dangle in np.linspace(rot_min, rot_max, n_steps): + # rotated and scale up to IMG_SCALE_UP-size + im_template = im.rotate(template, angle + dangle, center=center, scale=scale) + + # Perform template match operations + res = cv2.matchTemplate(im_source, im_template, cv2.TM_CCOEFF_NORMED) + (minVal, maxVal, minLoc, maxLoc) = cv2.minMaxLoc(res) + + r = maxVal + if r > best_r: + best_r = r + best_angle = dangle + best_max_loc = (maxLoc[0]/scale, maxLoc[1]/scale) + else: + break + + angle += best_angle + loc_x = best_max_loc[0] + loc_y = best_max_loc[1] + + return angle, (loc_x, loc_y) diff --git a/util.py b/util.py new file mode 100644 index 0000000..9542319 --- /dev/null +++ b/util.py @@ -0,0 +1,53 @@ +import cv2 +import sys +import numpy as np +import imutils as im + +(major_ver, minor_ver, subminor_ver) = cv2.__version__.split('.') +print(cv2.__version__) + + +def to_rect(bbox): + top_left = (bbox[0], bbox[1]) + bottom_right = (top_left[0] + bbox[2], top_left[1] + bbox[3]) + + return top_left, bottom_right + + +def bbox_add_position(bbox, bbox_add): + res = (bbox[0] + bbox_add[0], bbox[1] + bbox_add[1], bbox[2], bbox[3]) + return res + + +def bbox_round(src): + x = int(round(src[0])) + y = int(round(src[1])) + w = int(round(src[2])) + h = int(round(src[3])) + + return x, y, w, h + + +def bbox_extend(bbox: cv2.typing.Rect, factor: float = 0): + we2 = bbox[2]*factor/2 + he2 = bbox[3]*factor/2 + + return bbox_round((bbox[0]-we2, bbox[1]-he2, bbox[2]+2*we2, bbox[3]+2*he2)) + + +def image_crop(src, bbox: cv2.typing.Rect): + bb = bbox_round(bbox) + x = bb[0] + y = bb[1] + w = bb[2] + h = bb[3] + + dst = src[y:y+h, x:x+w] + return dst + + +def process_image_bbox(src): + # convert to gray + gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) + + return gray