-import numpy as np\r
-import cv2\r
-import os\r
-from contextlib import contextmanager\r
-import itertools as it\r
-\r
-image_extensions = ['.bmp', '.jpg', '.jpeg', '.png', '.tif', '.tiff', '.pbm', '.pgm', '.ppm']\r
-\r
-def splitfn(fn):\r
- path, fn = os.path.split(fn)\r
- name, ext = os.path.splitext(fn)\r
- return path, name, ext\r
-\r
-def anorm2(a):\r
- return (a*a).sum(-1)\r
-def anorm(a):\r
- return np.sqrt( anorm2(a) )\r
-\r
-def homotrans(H, x, y):\r
- xs = H[0, 0]*x + H[0, 1]*y + H[0, 2]\r
- ys = H[1, 0]*x + H[1, 1]*y + H[1, 2]\r
- s = H[2, 0]*x + H[2, 1]*y + H[2, 2]\r
- return xs/s, ys/s\r
-\r
-def to_rect(a):\r
- a = np.ravel(a)\r
- if len(a) == 2:\r
- a = (0, 0, a[0], a[1])\r
- return np.array(a, np.float64).reshape(2, 2)\r
-\r
-def rect2rect_mtx(src, dst):\r
- src, dst = to_rect(src), to_rect(dst)\r
- cx, cy = (dst[1] - dst[0]) / (src[1] - src[0])\r
- tx, ty = dst[0] - src[0] * (cx, cy)\r
- M = np.float64([[ cx, 0, tx],\r
- [ 0, cy, ty],\r
- [ 0, 0, 1]])\r
- return M\r
-\r
-\r
-def lookat(eye, target, up = (0, 0, 1)):\r
- fwd = np.asarray(target, np.float64) - eye\r
- fwd /= anorm(fwd)\r
- right = np.cross(fwd, up)\r
- right /= anorm(right)\r
- down = np.cross(fwd, right)\r
- R = np.float64([right, down, fwd])\r
- tvec = -np.dot(R, eye)\r
- return R, tvec\r
-\r
-def mtx2rvec(R):\r
- w, u, vt = cv2.SVDecomp(R - np.eye(3))\r
- p = vt[0] + u[:,0]*w[0] # same as np.dot(R, vt[0])\r
- c = np.dot(vt[0], p)\r
- s = np.dot(vt[1], p)\r
- axis = np.cross(vt[0], vt[1])\r
- return axis * np.arctan2(s, c)\r
-\r
-def draw_str(dst, (x, y), s):\r
- cv2.putText(dst, s, (x+1, y+1), cv2.FONT_HERSHEY_PLAIN, 1.0, (0, 0, 0), thickness = 2, lineType=cv2.CV_AA)\r
- cv2.putText(dst, s, (x, y), cv2.FONT_HERSHEY_PLAIN, 1.0, (255, 255, 255), lineType=cv2.CV_AA)\r
-\r
-class Sketcher:\r
- def __init__(self, windowname, dests, colors_func):\r
- self.prev_pt = None\r
- self.windowname = windowname\r
- self.dests = dests\r
- self.colors_func = colors_func\r
- self.dirty = False\r
- self.show()\r
- cv2.setMouseCallback(self.windowname, self.on_mouse)\r
-\r
- def show(self):\r
- cv2.imshow(self.windowname, self.dests[0])\r
-\r
- def on_mouse(self, event, x, y, flags, param):\r
- pt = (x, y)\r
- if event == cv2.EVENT_LBUTTONDOWN:\r
- self.prev_pt = pt\r
- if self.prev_pt and flags & cv2.EVENT_FLAG_LBUTTON:\r
- for dst, color in zip(self.dests, self.colors_func()):\r
- cv2.line(dst, self.prev_pt, pt, color, 5)\r
- self.dirty = True\r
- self.prev_pt = pt\r
- self.show()\r
- else:\r
- self.prev_pt = None\r
-\r
-\r
-# palette data from matplotlib/_cm.py\r
-_jet_data = {'red': ((0., 0, 0), (0.35, 0, 0), (0.66, 1, 1), (0.89,1, 1),\r
- (1, 0.5, 0.5)),\r
- 'green': ((0., 0, 0), (0.125,0, 0), (0.375,1, 1), (0.64,1, 1),\r
- (0.91,0,0), (1, 0, 0)),\r
- 'blue': ((0., 0.5, 0.5), (0.11, 1, 1), (0.34, 1, 1), (0.65,0, 0),\r
- (1, 0, 0))}\r
-\r
-cmap_data = { 'jet' : _jet_data }\r
-\r
-def make_cmap(name, n=256):\r
- data = cmap_data[name]\r
- xs = np.linspace(0.0, 1.0, n)\r
- channels = []\r
- eps = 1e-6\r
- for ch_name in ['blue', 'green', 'red']:\r
- ch_data = data[ch_name]\r
- xp, yp = [], []\r
- for x, y1, y2 in ch_data:\r
- xp += [x, x+eps]\r
- yp += [y1, y2]\r
- ch = np.interp(xs, xp, yp)\r
- channels.append(ch)\r
- return np.uint8(np.array(channels).T*255)\r
-\r
-def nothing(*arg, **kw):\r
- pass\r
-\r
-def clock():\r
- return cv2.getTickCount() / cv2.getTickFrequency()\r
-\r
-@contextmanager\r
-def Timer(msg):\r
- print msg, '...',\r
- start = clock()\r
- try:\r
- yield\r
- finally:\r
- print "%.2f ms" % ((clock()-start)*1000)\r
-\r
-class StatValue:\r
- def __init__(self, smooth_coef = 0.5):\r
- self.value = None\r
- self.smooth_coef = smooth_coef\r
- def update(self, v):\r
- if self.value is None:\r
- self.value = v\r
- else:\r
- c = self.smooth_coef\r
- self.value = c * self.value + (1.0-c) * v\r
-\r
-class RectSelector:\r
- def __init__(self, win, callback):\r
- self.win = win\r
- self.callback = callback\r
- cv2.setMouseCallback(win, self.onmouse)\r
- self.drag_start = None\r
- self.drag_rect = None\r
- def onmouse(self, event, x, y, flags, param):\r
- x, y = np.int16([x, y]) # BUG\r
- if event == cv2.EVENT_LBUTTONDOWN:\r
- self.drag_start = (x, y)\r
- if self.drag_start: \r
- if flags & cv2.EVENT_FLAG_LBUTTON:\r
- xo, yo = self.drag_start\r
- x0, y0 = np.minimum([xo, yo], [x, y])\r
- x1, y1 = np.maximum([xo, yo], [x, y])\r
- self.drag_rect = None\r
- if x1-x0 > 0 and y1-y0 > 0:\r
- self.drag_rect = (x0, y0, x1, y1)\r
- else:\r
- rect = self.drag_rect\r
- self.drag_start = None\r
- self.drag_rect = None\r
- if rect:\r
- self.callback(rect)\r
- def draw(self, vis):\r
- if not self.drag_rect:\r
- return False\r
- x0, y0, x1, y1 = self.drag_rect\r
- cv2.rectangle(vis, (x0, y0), (x1, y1), (0, 255, 0), 2)\r
- return True\r
- @property\r
- def dragging(self):\r
- return self.drag_rect is not None\r
-\r
-\r
-def grouper(n, iterable, fillvalue=None):\r
- '''grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx'''\r
- args = [iter(iterable)] * n\r
- return it.izip_longest(fillvalue=fillvalue, *args)\r
-\r
-def mosaic(w, imgs):\r
- '''Make a grid from images. \r
-\r
- w -- number of grid columns\r
- imgs -- images (must have same size and format)\r
- '''\r
- imgs = iter(imgs)\r
- img0 = imgs.next()\r
- pad = np.zeros_like(img0)\r
- imgs = it.chain([img0], imgs)\r
- rows = grouper(w, imgs, pad)\r
- return np.vstack(map(np.hstack, rows))\r
-\r
-def getsize(img):\r
- h, w = img.shape[:2]\r
- return w, h\r
-\r
-def mdot(*args):\r
- return reduce(np.dot, args)\r
+import numpy as np
+import cv2
+import os
+from contextlib import contextmanager
+import itertools as it
+
+image_extensions = ['.bmp', '.jpg', '.jpeg', '.png', '.tif', '.tiff', '.pbm', '.pgm', '.ppm']
+
+class Bunch(object):
+ def __init__(self, **kw):
+ self.__dict__.update(kw)
+ def __str__(self):
+ return str(self.__dict__)
+
+def splitfn(fn):
+ path, fn = os.path.split(fn)
+ name, ext = os.path.splitext(fn)
+ return path, name, ext
+
+def anorm2(a):
+ return (a*a).sum(-1)
+def anorm(a):
+ return np.sqrt( anorm2(a) )
+
+def homotrans(H, x, y):
+ xs = H[0, 0]*x + H[0, 1]*y + H[0, 2]
+ ys = H[1, 0]*x + H[1, 1]*y + H[1, 2]
+ s = H[2, 0]*x + H[2, 1]*y + H[2, 2]
+ return xs/s, ys/s
+
+def to_rect(a):
+ a = np.ravel(a)
+ if len(a) == 2:
+ a = (0, 0, a[0], a[1])
+ return np.array(a, np.float64).reshape(2, 2)
+
+def rect2rect_mtx(src, dst):
+ src, dst = to_rect(src), to_rect(dst)
+ cx, cy = (dst[1] - dst[0]) / (src[1] - src[0])
+ tx, ty = dst[0] - src[0] * (cx, cy)
+ M = np.float64([[ cx, 0, tx],
+ [ 0, cy, ty],
+ [ 0, 0, 1]])
+ return M
+
+
+def lookat(eye, target, up = (0, 0, 1)):
+ fwd = np.asarray(target, np.float64) - eye
+ fwd /= anorm(fwd)
+ right = np.cross(fwd, up)
+ right /= anorm(right)
+ down = np.cross(fwd, right)
+ R = np.float64([right, down, fwd])
+ tvec = -np.dot(R, eye)
+ return R, tvec
+
+def mtx2rvec(R):
+ w, u, vt = cv2.SVDecomp(R - np.eye(3))
+ p = vt[0] + u[:,0]*w[0] # same as np.dot(R, vt[0])
+ c = np.dot(vt[0], p)
+ s = np.dot(vt[1], p)
+ axis = np.cross(vt[0], vt[1])
+ return axis * np.arctan2(s, c)
+
+def draw_str(dst, (x, y), s):
+ cv2.putText(dst, s, (x+1, y+1), cv2.FONT_HERSHEY_PLAIN, 1.0, (0, 0, 0), thickness = 2, lineType=cv2.CV_AA)
+ cv2.putText(dst, s, (x, y), cv2.FONT_HERSHEY_PLAIN, 1.0, (255, 255, 255), lineType=cv2.CV_AA)
+
+class Sketcher:
+ def __init__(self, windowname, dests, colors_func):
+ self.prev_pt = None
+ self.windowname = windowname
+ self.dests = dests
+ self.colors_func = colors_func
+ self.dirty = False
+ self.show()
+ cv2.setMouseCallback(self.windowname, self.on_mouse)
+
+ def show(self):
+ cv2.imshow(self.windowname, self.dests[0])
+
+ def on_mouse(self, event, x, y, flags, param):
+ pt = (x, y)
+ if event == cv2.EVENT_LBUTTONDOWN:
+ self.prev_pt = pt
+ if self.prev_pt and flags & cv2.EVENT_FLAG_LBUTTON:
+ for dst, color in zip(self.dests, self.colors_func()):
+ cv2.line(dst, self.prev_pt, pt, color, 5)
+ self.dirty = True
+ self.prev_pt = pt
+ self.show()
+ else:
+ self.prev_pt = None
+
+
+# palette data from matplotlib/_cm.py
+_jet_data = {'red': ((0., 0, 0), (0.35, 0, 0), (0.66, 1, 1), (0.89,1, 1),
+ (1, 0.5, 0.5)),
+ 'green': ((0., 0, 0), (0.125,0, 0), (0.375,1, 1), (0.64,1, 1),
+ (0.91,0,0), (1, 0, 0)),
+ 'blue': ((0., 0.5, 0.5), (0.11, 1, 1), (0.34, 1, 1), (0.65,0, 0),
+ (1, 0, 0))}
+
+cmap_data = { 'jet' : _jet_data }
+
+def make_cmap(name, n=256):
+ data = cmap_data[name]
+ xs = np.linspace(0.0, 1.0, n)
+ channels = []
+ eps = 1e-6
+ for ch_name in ['blue', 'green', 'red']:
+ ch_data = data[ch_name]
+ xp, yp = [], []
+ for x, y1, y2 in ch_data:
+ xp += [x, x+eps]
+ yp += [y1, y2]
+ ch = np.interp(xs, xp, yp)
+ channels.append(ch)
+ return np.uint8(np.array(channels).T*255)
+
+def nothing(*arg, **kw):
+ pass
+
+def clock():
+ return cv2.getTickCount() / cv2.getTickFrequency()
+
+@contextmanager
+def Timer(msg):
+ print msg, '...',
+ start = clock()
+ try:
+ yield
+ finally:
+ print "%.2f ms" % ((clock()-start)*1000)
+
+class StatValue:
+ def __init__(self, smooth_coef = 0.5):
+ self.value = None
+ self.smooth_coef = smooth_coef
+ def update(self, v):
+ if self.value is None:
+ self.value = v
+ else:
+ c = self.smooth_coef
+ self.value = c * self.value + (1.0-c) * v
+
+class RectSelector:
+ def __init__(self, win, callback):
+ self.win = win
+ self.callback = callback
+ cv2.setMouseCallback(win, self.onmouse)
+ self.drag_start = None
+ self.drag_rect = None
+ def onmouse(self, event, x, y, flags, param):
+ x, y = np.int16([x, y]) # BUG
+ if event == cv2.EVENT_LBUTTONDOWN:
+ self.drag_start = (x, y)
+ if self.drag_start:
+ if flags & cv2.EVENT_FLAG_LBUTTON:
+ xo, yo = self.drag_start
+ x0, y0 = np.minimum([xo, yo], [x, y])
+ x1, y1 = np.maximum([xo, yo], [x, y])
+ self.drag_rect = None
+ if x1-x0 > 0 and y1-y0 > 0:
+ self.drag_rect = (x0, y0, x1, y1)
+ else:
+ rect = self.drag_rect
+ self.drag_start = None
+ self.drag_rect = None
+ if rect:
+ self.callback(rect)
+ def draw(self, vis):
+ if not self.drag_rect:
+ return False
+ x0, y0, x1, y1 = self.drag_rect
+ cv2.rectangle(vis, (x0, y0), (x1, y1), (0, 255, 0), 2)
+ return True
+ @property
+ def dragging(self):
+ return self.drag_rect is not None
+
+
+def grouper(n, iterable, fillvalue=None):
+ '''grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx'''
+ args = [iter(iterable)] * n
+ return it.izip_longest(fillvalue=fillvalue, *args)
+
+def mosaic(w, imgs):
+ '''Make a grid from images.
+
+ w -- number of grid columns
+ imgs -- images (must have same size and format)
+ '''
+ imgs = iter(imgs)
+ img0 = imgs.next()
+ pad = np.zeros_like(img0)
+ imgs = it.chain([img0], imgs)
+ rows = grouper(w, imgs, pad)
+ return np.vstack(map(np.hstack, rows))
+
+def getsize(img):
+ h, w = img.shape[:2]
+ return w, h
+
+def mdot(*args):
+ return reduce(np.dot, args)
+
+def draw_keypoints(vis, keypoints, color = (0, 255, 255)):
+ for kp in keypoints:
+ x, y = kp.pt
+ cv2.circle(vis, (int(x), int(y)), 2, color)
+
-'''\r
-Feature homography\r
-==================\r
-\r
-Example of using features2d framework for interactive video homography matching.\r
-ORB features and FLANN matcher are used.\r
-\r
-Inspired by http://www.youtube.com/watch?v=-ZNYoL8rzPY\r
-\r
-Usage\r
------\r
-feature_homography.py [<video source>]\r
-\r
-Select a textured planar object to track by drawing a box with a mouse.\r
-\r
-'''\r
-\r
-import numpy as np\r
-import cv2\r
-import video\r
-import common\r
-from collections import namedtuple\r
-from common import getsize\r
-\r
- \r
-FLANN_INDEX_KDTREE = 1\r
-FLANN_INDEX_LSH = 6\r
-flann_params= dict(algorithm = FLANN_INDEX_LSH,\r
- table_number = 6, # 12\r
- key_size = 12, # 20\r
- multi_probe_level = 1) #2\r
-\r
-MIN_MATCH_COUNT = 10\r
-\r
-\r
-ar_verts = np.float32([[0, 0, 0], [0, 1, 0], [1, 1, 0], [1, 0, 0],\r
- [0, 0, 1], [0, 1, 1], [1, 1, 1], [1, 0, 1], \r
- [0.5, 0.5, 2]])\r
-ar_edges = [(0, 1), (1, 2), (2, 3), (3, 0), \r
- (4, 5), (5, 6), (6, 7), (7, 4),\r
- (0, 4), (1, 5), (2, 6), (3, 7), \r
- (4, 8), (5, 8), (6, 8), (7, 8)]\r
-\r
-\r
-\r
-def draw_keypoints(vis, keypoints, color = (0, 255, 255)):\r
- for kp in keypoints:\r
- x, y = kp.pt\r
- cv2.circle(vis, (int(x), int(y)), 2, color)\r
-\r
-class App:\r
- def __init__(self, src):\r
- self.cap = video.create_capture(src)\r
- self.frame = None\r
- self.paused = False\r
- self.ref_frame = None\r
-\r
- self.detector = cv2.ORB( nfeatures = 1000 )\r
- self.matcher = cv2.FlannBasedMatcher(flann_params, {}) # bug : need to pass empty dict (#1329)\r
-\r
- cv2.namedWindow('plane')\r
- self.rect_sel = common.RectSelector('plane', self.on_rect)\r
-\r
-\r
- def match_frames(self):\r
- if len(self.frame_desc) < MIN_MATCH_COUNT or len(self.frame_desc) < MIN_MATCH_COUNT:\r
- return\r
- \r
- raw_matches = self.matcher.knnMatch(self.frame_desc, k = 2)\r
- p0, p1 = [], []\r
- for m in raw_matches:\r
- if len(m) == 2 and m[0].distance < m[1].distance * 0.75:\r
- m = m[0]\r
- p0.append( self.ref_points[m.trainIdx].pt ) # queryIdx\r
- p1.append( self.frame_points[m.queryIdx].pt )\r
- p0, p1 = np.float32((p0, p1))\r
- if len(p0) < MIN_MATCH_COUNT:\r
- return\r
-\r
- H, status = cv2.findHomography(p0, p1, cv2.RANSAC, 4.0)\r
- status = status.ravel() != 0\r
- if status.sum() < MIN_MATCH_COUNT:\r
- return\r
- p0, p1 = p0[status], p1[status]\r
- return p0, p1, H\r
-\r
-\r
- def on_frame(self, vis):\r
- match = self.match_frames()\r
- if match is None:\r
- return\r
- w, h = getsize(self.frame)\r
- p0, p1, H = match\r
- for (x0, y0), (x1, y1) in zip(np.int32(p0), np.int32(p1)):\r
- cv2.line(vis, (x0+w, y0), (x1, y1), (0, 255, 0))\r
- x0, y0, x1, y1 = self.ref_rect\r
- corners0 = np.float32([[x0, y0], [x1, y0], [x1, y1], [x0, y1]])\r
- img_corners = cv2.perspectiveTransform(corners0.reshape(1, -1, 2), H)\r
- cv2.polylines(vis, [np.int32(img_corners)], True, (255, 255, 255), 2)\r
-\r
- corners3d = np.hstack([corners0, np.zeros((4, 1), np.float32)])\r
- fx = 0.9\r
- K = np.float64([[fx*w, 0, 0.5*(w-1)],\r
- [0, fx*w, 0.5*(h-1)],\r
- [0.0,0.0, 1.0]])\r
- dist_coef = np.zeros(4)\r
- ret, rvec, tvec = cv2.solvePnP(corners3d, img_corners, K, dist_coef)\r
- verts = ar_verts * [(x1-x0), (y1-y0), -(x1-x0)*0.3] + (x0, y0, 0)\r
- verts = cv2.projectPoints(verts, rvec, tvec, K, dist_coef)[0].reshape(-1, 2)\r
- for i, j in ar_edges:\r
- (x0, y0), (x1, y1) = verts[i], verts[j]\r
- cv2.line(vis, (int(x0), int(y0)), (int(x1), int(y1)), (255, 255, 0), 2)\r
-\r
- def on_rect(self, rect):\r
- x0, y0, x1, y1 = rect\r
- self.ref_frame = self.frame.copy()\r
- self.ref_rect = rect\r
- points, descs = [], []\r
- for kp, desc in zip(self.frame_points, self.frame_desc):\r
- x, y = kp.pt\r
- if x0 <= x <= x1 and y0 <= y <= y1:\r
- points.append(kp)\r
- descs.append(desc)\r
- self.ref_points, self.ref_descs = points, np.uint8(descs)\r
-\r
- self.matcher.clear()\r
- self.matcher.add([self.ref_descs])\r
-\r
- def run(self):\r
- while True:\r
- playing = not self.paused and not self.rect_sel.dragging\r
- if playing or self.frame is None:\r
- ret, frame = self.cap.read()\r
- if not ret:\r
- break\r
- self.frame = np.fliplr(frame).copy()\r
- self.frame_points, self.frame_desc = self.detector.detectAndCompute(self.frame, None)\r
- if self.frame_desc is None: # detectAndCompute returns descs=None if not keypoints found\r
- self.frame_desc = []\r
- \r
- w, h = getsize(self.frame)\r
- vis = np.zeros((h, w*2, 3), np.uint8)\r
- vis[:h,:w] = self.frame\r
- if self.ref_frame is not None:\r
- vis[:h,w:] = self.ref_frame\r
- x0, y0, x1, y1 = self.ref_rect\r
- cv2.rectangle(vis, (x0+w, y0), (x1+w, y1), (0, 255, 0), 2)\r
- draw_keypoints(vis[:,w:], self.ref_points)\r
- draw_keypoints(vis, self.frame_points)\r
-\r
- if playing and self.ref_frame is not None:\r
- self.on_frame(vis)\r
- \r
- self.rect_sel.draw(vis)\r
- cv2.imshow('plane', vis)\r
- ch = cv2.waitKey(1)\r
- if ch == ord(' '):\r
- self.paused = not self.paused\r
- if ch == 27:\r
- break\r
-\r
-if __name__ == '__main__':\r
- print __doc__\r
-\r
- import sys\r
- try: video_src = sys.argv[1]\r
- except: video_src = 0\r
- App(video_src).run()\r
+'''
+Feature homography
+==================
+
+Example of using features2d framework for interactive video homography matching.
+ORB features and FLANN matcher are used. The actual tracking is implemented by
+PlaneTracker class in plane_tracker.py
+
+Inspired by http://www.youtube.com/watch?v=-ZNYoL8rzPY
+
+video: http://www.youtube.com/watch?v=FirtmYcC0Vc
+
+Usage
+-----
+feature_homography.py [<video source>]
+
+Keys:
+ SPACE - pause video
+
+Select a textured planar object to track by drawing a box with a mouse.
+'''
+
+import numpy as np
+import cv2
+import video
+import common
+from common import getsize, draw_keypoints
+from plane_tracker import PlaneTracker
+
+
+class App:
+ def __init__(self, src):
+ self.cap = video.create_capture(src)
+ self.frame = None
+ self.paused = False
+ self.tracker = PlaneTracker()
+
+ cv2.namedWindow('plane')
+ self.rect_sel = common.RectSelector('plane', self.on_rect)
+
+ def on_rect(self, rect):
+ self.tracker.clear()
+ self.tracker.add_target(self.frame, rect)
+
+ def run(self):
+ while True:
+ playing = not self.paused and not self.rect_sel.dragging
+ if playing or self.frame is None:
+ ret, frame = self.cap.read()
+ if not ret:
+ break
+ self.frame = np.frame.copy()
+
+ w, h = getsize(self.frame)
+ vis = np.zeros((h, w*2, 3), np.uint8)
+ vis[:h,:w] = self.frame
+ if len(self.tracker.targets) > 0:
+ target = self.tracker.targets[0]
+ vis[:,w:] = target.image
+ draw_keypoints(vis[:,w:], target.keypoints)
+ x0, y0, x1, y1 = target.rect
+ cv2.rectangle(vis, (x0+w, y0), (x1+w, y1), (0, 255, 0), 2)
+
+ if playing:
+ tracked = self.tracker.track(self.frame)
+ if len(tracked) > 0:
+ tracked = tracked[0]
+ cv2.polylines(vis, [np.int32(tracked.quad)], True, (255, 255, 255), 2)
+ for (x0, y0), (x1, y1) in zip(np.int32(tracked.p0), np.int32(tracked.p1)):
+ cv2.line(vis, (x0+w, y0), (x1, y1), (0, 255, 0))
+ draw_keypoints(vis, self.tracker.frame_points)
+
+ self.rect_sel.draw(vis)
+ cv2.imshow('plane', vis)
+ ch = cv2.waitKey(1)
+ if ch == ord(' '):
+ self.paused = not self.paused
+ if ch == 27:
+ break
+
+
+if __name__ == '__main__':
+ print __doc__
+
+ import sys
+ try: video_src = sys.argv[1]
+ except: video_src = 0
+ App(video_src).run()
--- /dev/null
+'''\r
+Planar augmented reality\r
+==================\r
+\r
+This sample shows an example of augmented reality overlay over a planar object\r
+tracked by PlaneTracker from plane_tracker.py. solvePnP funciton is used to\r
+estimate the tracked object location in 3d space.\r
+\r
+video: http://www.youtube.com/watch?v=pzVbhxx6aog\r
+\r
+Usage\r
+-----\r
+plane_ar.py [<video source>]\r
+\r
+Keys:\r
+ SPACE - pause video\r
+ c - clear targets\r
+\r
+Select a textured planar object to track by drawing a box with a mouse.\r
+Use 'focal' slider to adjust to camera focal length for proper video augmentation.\r
+'''\r
+\r
+import numpy as np\r
+import cv2\r
+import video\r
+import common\r
+from plane_tracker import PlaneTracker\r
+\r
+ \r
+ar_verts = np.float32([[0, 0, 0], [0, 1, 0], [1, 1, 0], [1, 0, 0],\r
+ [0, 0, 1], [0, 1, 1], [1, 1, 1], [1, 0, 1], \r
+ [0, 0.5, 2], [1, 0.5, 2]])\r
+ar_edges = [(0, 1), (1, 2), (2, 3), (3, 0), \r
+ (4, 5), (5, 6), (6, 7), (7, 4),\r
+ (0, 4), (1, 5), (2, 6), (3, 7), \r
+ (4, 8), (5, 8), (6, 9), (7, 9), (8, 9)]\r
+\r
+class App:\r
+ def __init__(self, src):\r
+ self.cap = video.create_capture(src)\r
+ self.frame = None\r
+ self.paused = False\r
+ self.tracker = PlaneTracker()\r
+\r
+ cv2.namedWindow('plane')\r
+ cv2.createTrackbar('focal', 'plane', 25, 50, common.nothing)\r
+ self.rect_sel = common.RectSelector('plane', self.on_rect)\r
+ \r
+ def on_rect(self, rect):\r
+ self.tracker.add_target(self.frame, rect)\r
+\r
+ def run(self):\r
+ while True:\r
+ playing = not self.paused and not self.rect_sel.dragging\r
+ if playing or self.frame is None:\r
+ ret, frame = self.cap.read()\r
+ if not ret:\r
+ break\r
+ self.frame = frame.copy()\r
+ \r
+ vis = self.frame.copy()\r
+ if playing:\r
+ tracked = self.tracker.track(self.frame)\r
+ for tr in tracked:\r
+ cv2.polylines(vis, [np.int32(tr.quad)], True, (255, 255, 255), 2)\r
+ for (x, y) in np.int32(tr.p1):\r
+ cv2.circle(vis, (x, y), 2, (255, 255, 255))\r
+ self.draw_overlay(vis, tr)\r
+\r
+ self.rect_sel.draw(vis)\r
+ cv2.imshow('plane', vis)\r
+ ch = cv2.waitKey(1)\r
+ if ch == ord(' '):\r
+ self.paused = not self.paused\r
+ if ch == ord('c'):\r
+ self.tracker.clear()\r
+ if ch == 27:\r
+ break\r
+\r
+ def draw_overlay(self, vis, tracked):\r
+ x0, y0, x1, y1 = tracked.target.rect\r
+ quad_3d = np.float32([[x0, y0, 0], [x1, y0, 0], [x1, y1, 0], [x0, y1, 0]])\r
+ fx = 0.5 + cv2.getTrackbarPos('focal', 'plane') / 50.0\r
+ h, w = vis.shape[:2]\r
+ K = np.float64([[fx*w, 0, 0.5*(w-1)],\r
+ [0, fx*w, 0.5*(h-1)],\r
+ [0.0,0.0, 1.0]])\r
+ dist_coef = np.zeros(4)\r
+ ret, rvec, tvec = cv2.solvePnP(quad_3d, tracked.quad, K, dist_coef)\r
+ verts = ar_verts * [(x1-x0), (y1-y0), -(x1-x0)*0.3] + (x0, y0, 0)\r
+ verts = cv2.projectPoints(verts, rvec, tvec, K, dist_coef)[0].reshape(-1, 2)\r
+ for i, j in ar_edges:\r
+ (x0, y0), (x1, y1) = verts[i], verts[j]\r
+ cv2.line(vis, (int(x0), int(y0)), (int(x1), int(y1)), (255, 255, 0), 2)\r
+\r
+\r
+if __name__ == '__main__':\r
+ print __doc__\r
+\r
+ import sys\r
+ try: video_src = sys.argv[1]\r
+ except: video_src = 0\r
+ App(video_src).run()\r
--- /dev/null
+'''\r
+Multitarget planar tracking\r
+==================\r
+\r
+Example of using features2d framework for interactive video homography matching.\r
+ORB features and FLANN matcher are used. This sample provides PlaneTracker class\r
+and an example of its usage.\r
+\r
+video: http://www.youtube.com/watch?v=pzVbhxx6aog\r
+\r
+Usage\r
+-----\r
+plane_tracker.py [<video source>]\r
+\r
+Keys:\r
+ SPACE - pause video\r
+ c - clear targets\r
+\r
+Select a textured planar object to track by drawing a box with a mouse.\r
+'''\r
+\r
+import numpy as np\r
+import cv2\r
+from collections import namedtuple\r
+import video\r
+import common\r
+\r
+\r
+FLANN_INDEX_KDTREE = 1\r
+FLANN_INDEX_LSH = 6\r
+flann_params= dict(algorithm = FLANN_INDEX_LSH,\r
+ table_number = 6, # 12\r
+ key_size = 12, # 20\r
+ multi_probe_level = 1) #2\r
+\r
+MIN_MATCH_COUNT = 10\r
+\r
+'''\r
+ image - image to track\r
+ rect - tracked rectangle (x1, y1, x2, y2)\r
+ keypoints - keypoints detected inside rect\r
+ descrs - their descriptors\r
+ data - some user-provided data\r
+'''\r
+PlanarTarget = namedtuple('PlaneTarget', 'image, rect, keypoints, descrs, data')\r
+\r
+'''\r
+ target - reference to PlanarTarget\r
+ p0 - matched points coords in target image\r
+ p1 - matched points coords in input frame\r
+ H - homography matrix from p0 to p1\r
+ quad - target bounary quad in input frame\r
+'''\r
+TrackedTarget = namedtuple('TrackedTarget', 'target, p0, p1, H, quad')\r
+\r
+class PlaneTracker:\r
+ def __init__(self):\r
+ self.detector = cv2.ORB( nfeatures = 1000 )\r
+ self.matcher = cv2.FlannBasedMatcher(flann_params, {}) # bug : need to pass empty dict (#1329)\r
+ self.targets = []\r
+\r
+ def add_target(self, image, rect, data=None):\r
+ '''Add a new tracking target.'''\r
+ x0, y0, x1, y1 = rect\r
+ raw_points, raw_descrs = self.detect_features(image)\r
+ points, descs = [], []\r
+ for kp, desc in zip(raw_points, raw_descrs):\r
+ x, y = kp.pt\r
+ if x0 <= x <= x1 and y0 <= y <= y1:\r
+ points.append(kp)\r
+ descs.append(desc)\r
+ descs = np.uint8(descs)\r
+ self.matcher.add([descs])\r
+ target = PlanarTarget(image = image, rect=rect, keypoints = points, descrs=descs, data=None)\r
+ self.targets.append(target)\r
+\r
+ def clear(self):\r
+ '''Remove all targets'''\r
+ self.targets = []\r
+ self.matcher.clear()\r
+\r
+ def track(self, frame):\r
+ '''Returns a list of detected TrackedTarget objects'''\r
+ self.frame_points, self.frame_descrs = self.detect_features(frame)\r
+ if len(self.frame_points) < MIN_MATCH_COUNT:\r
+ return []\r
+ matches = self.matcher.knnMatch(self.frame_descrs, k = 2)\r
+ matches = [m[0] for m in matches if len(m) == 2 and m[0].distance < m[1].distance * 0.75]\r
+ if len(matches) < MIN_MATCH_COUNT:\r
+ return []\r
+ matches_by_id = [[] for _ in xrange(len(self.targets))]\r
+ for m in matches:\r
+ matches_by_id[m.imgIdx].append(m)\r
+ tracked = []\r
+ for imgIdx, matches in enumerate(matches_by_id):\r
+ if len(matches) < MIN_MATCH_COUNT:\r
+ continue\r
+ target = self.targets[imgIdx]\r
+ p0 = [target.keypoints[m.trainIdx].pt for m in matches]\r
+ p1 = [self.frame_points[m.queryIdx].pt for m in matches]\r
+ p0, p1 = np.float32((p0, p1))\r
+ H, status = cv2.findHomography(p0, p1, cv2.RANSAC, 3.0)\r
+ status = status.ravel() != 0\r
+ if status.sum() < MIN_MATCH_COUNT:\r
+ continue\r
+ p0, p1 = p0[status], p1[status]\r
+ \r
+ x0, y0, x1, y1 = target.rect\r
+ quad = np.float32([[x0, y0], [x1, y0], [x1, y1], [x0, y1]])\r
+ quad = cv2.perspectiveTransform(quad.reshape(1, -1, 2), H).reshape(-1, 2)\r
+\r
+ track = TrackedTarget(target=target, p0=p0, p1=p1, H=H, quad=quad)\r
+ tracked.append(track)\r
+ tracked.sort(key = lambda t: len(t.p0), reverse=True)\r
+ return tracked\r
+\r
+ def detect_features(self, frame):\r
+ '''detect_features(self, frame) -> keypoints, descrs'''\r
+ keypoints, descrs = self.detector.detectAndCompute(frame, None)\r
+ if descrs is None: # detectAndCompute returns descs=None if not keypoints found\r
+ descrs = []\r
+ return keypoints, descrs\r
+\r
+\r
+class App:\r
+ def __init__(self, src):\r
+ self.cap = video.create_capture(src)\r
+ self.frame = None\r
+ self.paused = False\r
+ self.tracker = PlaneTracker()\r
+\r
+ cv2.namedWindow('plane')\r
+ self.rect_sel = common.RectSelector('plane', self.on_rect)\r
+ \r
+ def on_rect(self, rect):\r
+ self.tracker.add_target(self.frame, rect)\r
+\r
+ def run(self):\r
+ while True:\r
+ playing = not self.paused and not self.rect_sel.dragging\r
+ if playing or self.frame is None:\r
+ ret, frame = self.cap.read()\r
+ if not ret:\r
+ break\r
+ self.frame = frame.copy()\r
+ \r
+ vis = self.frame.copy()\r
+ if playing:\r
+ tracked = self.tracker.track(self.frame)\r
+ for tr in tracked:\r
+ cv2.polylines(vis, [np.int32(tr.quad)], True, (255, 255, 255), 2)\r
+ for (x, y) in np.int32(tr.p1):\r
+ cv2.circle(vis, (x, y), 2, (255, 255, 255))\r
+\r
+ self.rect_sel.draw(vis)\r
+ cv2.imshow('plane', vis)\r
+ ch = cv2.waitKey(1)\r
+ if ch == ord(' '):\r
+ self.paused = not self.paused\r
+ if ch == ord('c'):\r
+ self.tracker.clear()\r
+ if ch == 27:\r
+ break\r
+\r
+if __name__ == '__main__':\r
+ print __doc__\r
+\r
+ import sys\r
+ try: video_src = sys.argv[1]\r
+ except: video_src = 0\r
+ App(video_src).run()\r