new/improved Python samples by Alexander Mordvintsev
authorVadim Pisarevsky <vadim.pisarevsky@itseez.com>
Mon, 6 Aug 2012 12:35:35 +0000 (16:35 +0400)
committerVadim Pisarevsky <vadim.pisarevsky@itseez.com>
Mon, 6 Aug 2012 12:35:35 +0000 (16:35 +0400)
samples/python2/common.py
samples/python2/feature_homography.py
samples/python2/plane_ar.py [new file with mode: 0755]
samples/python2/plane_tracker.py [new file with mode: 0755]

index 0f332b6..89f8b77 100644 (file)
-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)
+
index d553deb..1eae58a 100644 (file)
-'''\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()
diff --git a/samples/python2/plane_ar.py b/samples/python2/plane_ar.py
new file mode 100755 (executable)
index 0000000..bb2b5aa
--- /dev/null
@@ -0,0 +1,103 @@
+'''\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
diff --git a/samples/python2/plane_tracker.py b/samples/python2/plane_tracker.py
new file mode 100755 (executable)
index 0000000..32e8455
--- /dev/null
@@ -0,0 +1,171 @@
+'''\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