├── .gitignore ├── LICENSE.txt ├── README.rst ├── rectangle_tracker.jpg ├── rectangle_tracker.py └── sample.avi /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .* 3 | !.gitignore 4 | !.gitattributes -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Andrew Allan Port 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Python/OpenCV Rectangle Tracker 2 | =============================== 3 | 4 | Designed to track a piece of letter-size printer paper while it's being written on. Uses python 2, numpy, and opencv. 5 | 6 | .. image:: https://rawgit.com/mathandy/python-opencv-rectangle-tracker/master/rectangle_tracker.jpg 7 | 8 | For Help 9 | -------- 10 | Contact me, AndyAPort@gmail.com 11 | 12 | Licence 13 | ------- 14 | 15 | This code and sample video are available for use under the conditions of the Apache License 2.0. 16 | -------------------------------------------------------------------------------- /rectangle_tracker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathandy/python-opencv-rectangle-tracker/eea29d90222428b77f99af596d52e1c5df3837e2/rectangle_tracker.jpg -------------------------------------------------------------------------------- /rectangle_tracker.py: -------------------------------------------------------------------------------- 1 | """ 2 | Notes: 3 | 1) This is algorithm is primarily designed for a rectangular piece of 4 | paper lying flat on a flat surface, but may work in other situations 5 | assuming that the paper's corners are not obstructed. 6 | 2) The camera's view of the paper must be unobstructed in first frame. 7 | 8 | """ 9 | 10 | # Basic Dependencies 11 | from __future__ import division, print_function 12 | from math import ceil, acos 13 | from time import time 14 | 15 | # External Dependencies 16 | import numpy as np 17 | from numpy.linalg import norm 18 | import cv2 19 | 20 | 21 | # Default User Parameters 22 | VIDEO_FILE_LOCATION = 'sample.avi' 23 | FPS_INTERVAL = 10 # updates FPS estimate after this many frames 24 | MHI_DURATION = 10 # max frames remembered by motion history 25 | FRAME_WIDTH, FRAME_HEIGHT = 640, 424 26 | PAPER_RATIO = 11/8.5 # height/width of paper 27 | ROT180 = False # If paper is upside down, change this 28 | REDUCE_DISPLAY_SIZE = False # Use if output window is too big for your screen 29 | 30 | 31 | # Internal Parameters 32 | tol_corner_movement = 1 33 | obst_tol = 10 # used to determine tolerance 34 | closing_iterations = 10 35 | show_thresholding = False # Use to display thresholding 36 | 37 | def rotate180(im): 38 | """Rotates an image by 180 degrees.""" 39 | return cv2.flip(im, -1) 40 | 41 | 42 | def persTransform(pts, H): 43 | """Transforms a list of points, `pts`, 44 | using the perspective transform `H`.""" 45 | src = np.zeros((len(pts), 1, 2)) 46 | src[:, 0] = pts 47 | dst = cv2.perspectiveTransform(src, H) 48 | return np.array(dst[:, 0, :], dtype='float32') 49 | 50 | 51 | def affTransform(pts, A): 52 | """Transforms a list of points, `pts`, 53 | using the affine transform `A`.""" 54 | src = np.zeros((len(pts), 1, 2)) 55 | src[:, 0] = pts 56 | dst = cv2.transform(src, A) 57 | return np.array(dst[:, 0, :], dtype='float32') 58 | 59 | 60 | def draw_polygon(im, vertices, vertex_colors=None, edge_colors=None, 61 | alter_input_image=False, draw_edges=True, draw_vertices=True, 62 | display=False, title='', pause=False): 63 | """returns image with polygon drawn on it.""" 64 | _default_vertex_color = (255, 0, 0) 65 | _default_edge_color = (255, 0, 0) 66 | if not alter_input_image: 67 | im2 = im.copy() 68 | else: 69 | im2 = im 70 | if vertices is not None: 71 | N = len(vertices) 72 | vertices = [tuple(v) for v in vertices] 73 | if vertex_colors is None: 74 | vertex_colors = [_default_vertex_color] * N 75 | if edge_colors is None: 76 | edge_colors = [_default_edge_color] * N 77 | for i in range(N): 78 | startpt = vertices[(i - 1) % N] 79 | endpt = vertices[i] 80 | if draw_vertices: 81 | cv2.circle(im2, startpt, 3, vertex_colors[(i - 1) % N], -1) 82 | if draw_edges: 83 | cv2.line(im2, startpt, endpt, edge_colors[(i - 1) % N], 2) 84 | if display: 85 | cv2.imshow(title, im2) 86 | # Note: `0xFF == ord('q')`is apparently necessary for 64bit machines 87 | if pause and cv2.waitKey(0) & 0xFF == ord('q'): 88 | pass 89 | return im2 90 | 91 | 92 | def run_main(): 93 | 94 | # Initialize some variables 95 | frame = None 96 | old_homog = None 97 | old_inv_homog = None 98 | corner_history = [] 99 | 100 | video_feed = cv2.VideoCapture(VIDEO_FILE_LOCATION) 101 | video_feed.set(cv2.cv.CV_CAP_PROP_FRAME_WIDTH, FRAME_WIDTH) 102 | video_feed.set(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT, FRAME_HEIGHT) 103 | 104 | frame_count = 0 105 | fps_time = time() 106 | while True: 107 | # initialize some stuff 108 | c_colors = [(0, 0, 255)] * 4 109 | 110 | # grab current frame from video_feed 111 | previous_frame = frame 112 | _, frame = video_feed.read() 113 | 114 | # Report FPS 115 | if not (frame_count % 10): 116 | fps = FPS_INTERVAL/(time() - fps_time) 117 | print('Frame:', frame_count, ' | FPS:', fps) 118 | fps_time = time() 119 | frame_count += 1 120 | 121 | # Convert to grayscale 122 | try: 123 | gray_img = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 124 | except: 125 | print("\nVideo feed ended.\n") 126 | break 127 | 128 | # get binary thresholding of image 129 | gray_smooth = cv2.GaussianBlur(gray_img, (15, 15), 0) 130 | _, bin_img = cv2.threshold(gray_smooth, 100, 255, cv2.THRESH_BINARY) 131 | 132 | # morphological closing 133 | kernel = np.ones((3, 3), np.uint8) 134 | bin_img = cv2.morphologyEx(bin_img, cv2.MORPH_CLOSE, 135 | kernel, iterations=closing_iterations) 136 | 137 | # Find corners. To do this: 138 | # 1) Find the largest (area) contour in frame (after thresholding) 139 | # 2) get contours convex hull, 140 | # 3) reduce degree of convex hull with Douglas-Peucker algorithm, 141 | # 4) refine corners with subpixel corner finder 142 | 143 | # step 1 144 | contours, _ = cv2.findContours(bin_img, cv2.RETR_EXTERNAL, 145 | cv2.CHAIN_APPROX_SIMPLE) 146 | biggest_contour = max(contours, key=cv2.contourArea) 147 | 148 | # step 2 149 | hull = cv2.convexHull(biggest_contour) 150 | epsilon = 0.05 * cv2.arcLength(biggest_contour, True) 151 | 152 | # step 3 153 | hull = cv2.approxPolyDP(hull, epsilon, True) 154 | 155 | # step 4 156 | hull = np.float32(hull) 157 | method = cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT 158 | criteria = (method, 1000, 1e-4) 159 | cv2.cornerSubPix(gray_img, hull, (5, 5), (-1, -1), criteria) 160 | corners = [pt[0] for pt in hull] 161 | 162 | # Find top-right corner and use to label corners 163 | # Note: currently corners are in CW order 164 | # Note: ordering will be checked below against expected corners 165 | tr_index = np.argmin(c[0] + c[1] for c in corners) 166 | tl = corners[tr_index] 167 | bl = corners[(tr_index - 1) % 4] 168 | br = corners[(tr_index - 2) % 4] 169 | tr = corners[(tr_index - 3) % 4] 170 | 171 | # reformat and ensure that ordering is as expected below 172 | corners = np.float32([[c[0], c[1]] for c in [tl, bl, br, tr]]) 173 | 174 | # IMPORTANT ASSUMPTIONS on paper tracking (used in code block below): 175 | # 1) if any one point is stationary from previous frame, then all 176 | # are stationary with probability 1. 177 | # 2) if any corners are obstructed, assume the paper is still flat 178 | # against the same plane as it was in the previous frame. 179 | # I.e. the transformation from previous frame to this frame should 180 | # be of the form of a translation and a rotation in said plane. 181 | # 3) see code comments for additional assumptions, haha, sorry 182 | 183 | def get_edge_lengths(topl, botl, botr, topr): 184 | """ Takes in list of four corners, returns four edge lengths 185 | in order top, right, bottom, left.""" 186 | tbrl = [topr - topl, topr - botr, botr - botl, botl - topl] 187 | return [norm(edge) for edge in tbrl] 188 | 189 | if not corner_history: 190 | last_unob_corners = corners 191 | else: 192 | # determine expected corner locations and edge lengths 193 | expected_corners = last_unob_corners 194 | if last_unob_corners is None: 195 | expected_lengths = get_edge_lengths(*expected_corners) 196 | else: 197 | expected_lengths = get_edge_lengths(*last_unob_corners) 198 | 199 | # check ordering 200 | def cyclist(lst, k): 201 | if k: 202 | return [lst[(i+k)%len(lst)] for i in xrange(len(lst))] 203 | return lst 204 | 205 | def _order_dist(offset): 206 | offset_corners = corners[cyclist(range(4), offset)] 207 | return norm(expected_corners - offset_corners), offset_corners 208 | if corner_history: 209 | corners = min(_order_dist(k) for k in range(4))[1] 210 | 211 | # Look for obstructions by looking for changes in edge lengths 212 | # Note: these lengths are not perspective invariant 213 | # TODO: checking by Hessian may be a better method 214 | new_lengths = get_edge_lengths(*corners) 215 | top_is_bad, rgt_is_bad, bot_is_bad, lft_is_bad = \ 216 | [abs(l0 - l1) > obst_tol for l1, l0 in 217 | zip(new_lengths, expected_lengths)] 218 | tl_ob = top_is_bad and lft_is_bad 219 | bl_ob = bot_is_bad and lft_is_bad 220 | br_ob = bot_is_bad and rgt_is_bad 221 | tr_ob = top_is_bad and rgt_is_bad 222 | 223 | is_obstr = [tl_ob, bl_ob, br_ob, tr_ob] 224 | ob_indices = [i for i, c in enumerate(is_obstr) if c] 225 | ob_corner_ct = sum(is_obstr) 226 | c_colors = [(0, 255, 0) if b else (0, 0, 255) for b in is_obstr] 227 | 228 | # Find difference of corners from expected location 229 | diffs = [norm(c - ec) for c, ec in zip(corners, expected_corners)] 230 | has_moved = [d > tol_corner_movement for d in diffs] 231 | 232 | # Check if paper has likely moved 233 | if sum(has_moved) < 4: 234 | # assume all is cool, just trust the corners found 235 | corners = last_unob_corners 236 | pass 237 | else: 238 | if sum(has_moved) == 1: 239 | # only one corner has moved, just assume it's obstructed 240 | # and replace it with the expected location 241 | bad_corner_idx = np.argmax(diffs) 242 | corners[bad_corner_idx] = expected_corners[bad_corner_idx] 243 | 244 | else: # find paper's affine transformation in expected plane 245 | print("frame={} | ob_corner_ct={}" 246 | "".format(frame_count, ob_corner_ct)) 247 | 248 | if sum(is_obstr) in (1, 2, 3): 249 | eco = zip(expected_corners, is_obstr) 250 | exp_unob = np.float32([c for c, b in eco if not b]) 251 | exp_ob = np.float32([c for c, b in eco if b]) 252 | co = zip(corners, is_obstr) 253 | new_unob = np.float32([c for c, b in co if not b]) 254 | 255 | exp_unob_pp = persTransform(exp_unob, old_homog) 256 | exp_ob_pp = persTransform(exp_ob, old_homog) 257 | new_unob_pp = persTransform(new_unob, old_homog) 258 | 259 | # check for obstructions 260 | if sum(is_obstr) == 0: # yay! no obstructed corners! 261 | pass 262 | 263 | elif sum(is_obstr) == 1: 264 | # Find the affine transformation in the paper's plane 265 | # from expected locations of the three unobstructed 266 | # corners to the found locations, then use this to 267 | # estimate the obstructed corner's location 268 | A = cv2.getAffineTransform(exp_unob_pp, new_unob_pp) 269 | new_ob_pp = affTransform(exp_ob_pp, A) 270 | new_ob = persTransform(new_ob_pp, old_inv_homog) 271 | corners[np.ix_(ob_indices)] = new_ob 272 | 273 | elif sum(is_obstr) == 2: 274 | # Align the line between the good corners 275 | # with the same line w.r.t the old corners 276 | p1, q1 = new_unob_pp[0], new_unob_pp[1] 277 | p0, q0 = exp_unob_pp[0], exp_unob_pp[1] 278 | u0 = (q0 - p0) / norm(q0 - p0) 279 | u1 = (q1 - p1) / norm(q1 - p1) 280 | angle = acos(np.dot(u0, u1)) # unsigned 281 | trans = p1 - p0 282 | 283 | # Find rotation that moves u0 to u1 284 | rotat = cv2.getRotationMatrix2D(tuple(p1), angle, 1) 285 | rotat = rotat[:, :2] 286 | 287 | # Expensive sign check for angle (could be improved) 288 | if norm(np.dot(u0, rotat) - u1) > norm(np.dot(u1, rotat) - u0): 289 | rotat = np.linalg.inv(rotat) 290 | 291 | # transform the old coords of the hidden corners 292 | # and map them back to the paper plane 293 | exp_ob_pp += trans 294 | new_ob_pp = affTransform(exp_ob_pp, rotat) 295 | new_ob = persTransform(new_ob_pp, old_inv_homog) 296 | corners[np.ix_(ob_indices)] = new_ob 297 | 298 | elif sum(is_obstr) in (3, 4): 299 | print("Uh oh, {} corners obstructed..." 300 | "".format(ob_corner_ct)) 301 | corners = expected_corners 302 | else: 303 | raise Exception("This should never happen.") 304 | 305 | 306 | # Homography 307 | w = max(abs(br[0] - bl[0]), 308 | abs(tr[0] - tl[0])) # width of paper in pixels 309 | h = PAPER_RATIO * w 310 | corners_pp = np.float32([[0, 0], [0, h], [w, h], [w, 0]]) 311 | homog, mask = cv2.findHomography(corners, corners_pp) 312 | inv_homog, inv_mask = cv2.findHomography(corners_pp, corners) 313 | paper = cv2.warpPerspective(frame, homog, (int(ceil(w)), int(ceil(h)))) 314 | if ROT180: 315 | paper = rotate180(paper) 316 | 317 | 318 | # Draw detected paper boundary on frame 319 | segmented_frame = draw_polygon(frame, corners, c_colors) 320 | 321 | # Resize paper to simplify display 322 | h = segmented_frame.shape[0] 323 | paper_w = int(round(h*paper.shape[1]/paper.shape[0])) 324 | resized_paper = cv2.resize(paper, (paper_w, h)) 325 | 326 | # Display 327 | big_img = np.hstack((segmented_frame, resized_paper)) 328 | if show_thresholding: 329 | bin_img = cv2.cvtColor(bin_img, cv2.COLOR_GRAY2BGR) 330 | big_img = np.hstack((big_img, bin_img)) 331 | if REDUCE_DISPLAY_SIZE: 332 | reduced_size = tuple(np.array(big_img.shape[:2][::-1])//2) 333 | smaller_big_img = cv2.resize(big_img, reduced_size) 334 | cv2.imshow('', big_img) 335 | 336 | 337 | # Updates for next iteration 338 | corner_history.append(corners) 339 | old_homog = homog 340 | old_inv_homog = inv_homog 341 | 342 | 343 | # this is apparently necessary for 64bit machines 344 | if cv2.waitKey(1) & 0xFF == ord('q'): 345 | break 346 | 347 | video_feed.release() 348 | cv2.destroyAllWindows() 349 | 350 | 351 | if __name__ == "__main__": 352 | try: 353 | run_main() 354 | except: 355 | cv2.destroyAllWindows() 356 | raise 357 | -------------------------------------------------------------------------------- /sample.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathandy/python-opencv-rectangle-tracker/eea29d90222428b77f99af596d52e1c5df3837e2/sample.avi --------------------------------------------------------------------------------