├── .gitignore ├── BaseStructuredForests.py ├── LICENSE ├── README.md ├── RandomForests.py ├── RobustPCA.py ├── StructuredForests.py ├── _RandomForests.pyx ├── _RandomForests.pyxbld ├── _StructuredForests.pyx ├── _random_forests.cpp ├── _random_forests.h ├── _utils.pyx ├── model └── forests │ └── forest.h5 ├── toy └── BSDS500 │ └── data │ ├── groundTruth │ ├── test │ │ ├── 16068.mat │ │ ├── 196062.mat │ │ ├── 296028.mat │ │ └── 335094.mat │ └── train │ │ ├── 12003.mat │ │ ├── 2092.mat │ │ ├── 8049.mat │ │ └── 8143.mat │ └── images │ ├── test │ ├── 16068.jpg │ ├── 196062.jpg │ ├── 296028.jpg │ └── 335094.jpg │ └── train │ ├── 12003.jpg │ ├── 2092.jpg │ ├── 8049.jpg │ └── 8143.jpg └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *.pyxbldc 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .cache 41 | nosetests.xml 42 | coverage.xml 43 | 44 | # Translations 45 | *.mo 46 | *.pot 47 | 48 | # Django stuff: 49 | *.log 50 | 51 | # Sphinx documentation 52 | docs/_build/ 53 | 54 | # PyBuilder 55 | target/ 56 | 57 | -------------------------------------------------------------------------------- /BaseStructuredForests.py: -------------------------------------------------------------------------------- 1 | __author__ = 'artanis' 2 | 3 | import cv2 4 | import numpy as N 5 | from skimage.util import view_as_windows 6 | from utils import resize, conv_tri, rgb2luv, gradient, histogram, pdist 7 | 8 | 9 | class BaseStructuredForests(object): 10 | def __init__(self, options): 11 | """ 12 | :param options: 13 | rgbd: 0 for RGB, 1 for RGB + depth 14 | shrink: amount to shrink channels 15 | n_orient: number of orientations per gradient scale 16 | grd_smooth_rad: radius for image gradient smoothing 17 | grd_norm_rad: radius for gradient normalization 18 | reg_smooth_rad: radius for reg channel smoothing 19 | ss_smooth_rad: radius for sim channel smoothing 20 | p_size: size of image patches 21 | n_cell: number of self similarity cells 22 | """ 23 | 24 | self.options = options 25 | assert self.options["p_size"] % 2 == 0 26 | 27 | def get_ftr_dim(self): 28 | shrink = self.options["shrink"] 29 | p_size = self.options["p_size"] 30 | n_cell = self.options["n_cell"] 31 | 32 | n_color_ch = 3 if self.options["rgbd"] == 0 else 4 33 | n_grad_ch = 2 * (1 + self.options["n_orient"]) 34 | n_ch = n_color_ch + n_grad_ch 35 | 36 | reg_ftr_dim = (p_size / shrink) ** 2 * n_ch 37 | ss_ftr_dim = n_cell ** 2 * (n_cell ** 2 - 1) / 2 * n_ch 38 | 39 | return reg_ftr_dim, ss_ftr_dim 40 | 41 | def get_shrunk_channels(self, src): 42 | shrink = self.options["shrink"] 43 | n_orient = self.options["n_orient"] 44 | grd_smooth_rad = self.options["grd_smooth_rad"] 45 | grd_norm_rad = self.options["grd_norm_rad"] 46 | 47 | luv = rgb2luv(src) 48 | size = (luv.shape[0] / shrink, luv.shape[1] / shrink) 49 | channels = [resize(luv, size)] 50 | 51 | for scale in [1.0, 0.5]: 52 | img = resize(luv, (luv.shape[0] * scale, luv.shape[1] * scale)) 53 | img = conv_tri(img, grd_smooth_rad) 54 | 55 | magnitude, orientation = gradient(img, grd_norm_rad) 56 | 57 | downscale = max(1, int(shrink * scale)) 58 | hist = histogram(magnitude, orientation, downscale, n_orient) 59 | 60 | channels.append(resize(magnitude, size)[:, :, None]) 61 | channels.append(resize(hist, size)) 62 | 63 | channels = N.concatenate(channels, axis=2) 64 | 65 | reg_smooth_rad = self.options["reg_smooth_rad"] / float(shrink) 66 | ss_smooth_rad = self.options["ss_smooth_rad"] / float(shrink) 67 | 68 | if reg_smooth_rad > 1.0: 69 | reg_ch = conv_tri(channels, int(round(reg_smooth_rad))) 70 | else: 71 | reg_ch = conv_tri(channels, reg_smooth_rad) 72 | 73 | if ss_smooth_rad > 1.0: 74 | ss_ch = conv_tri(channels, int(round(ss_smooth_rad))) 75 | else: 76 | ss_ch = conv_tri(channels, ss_smooth_rad) 77 | 78 | return reg_ch, ss_ch 79 | 80 | def get_shrunk_loc(self, pos): 81 | shrink = self.options["shrink"] 82 | 83 | return [(r / shrink, c / shrink) for r, c in pos] 84 | 85 | def get_reg_ftr(self, channels, smp_loc=None): 86 | """ 87 | Compute regular features. 88 | 89 | :param channels: shrunk channels for regular features 90 | :param smp_loc: shrunk sample locations (None for all) 91 | :return: regular features 92 | """ 93 | 94 | shrink = self.options["shrink"] 95 | p_size = self.options["p_size"] / shrink 96 | n_r, n_c, n_ch = channels.shape 97 | 98 | reg_ftr = view_as_windows(channels, (p_size, p_size, n_ch)) 99 | reg_ftr = reg_ftr.reshape((n_r - p_size + 1, n_c - p_size + 1, 100 | p_size ** 2 * n_ch)) 101 | 102 | if smp_loc is not None: 103 | r_pos = [r - p_size / 2 for r, _ in smp_loc] 104 | c_pos = [c - p_size / 2 for _, c in smp_loc] 105 | reg_ftr = reg_ftr[r_pos, c_pos] 106 | 107 | return reg_ftr 108 | 109 | def get_ss_ftr(self, channels, smp_loc=None): 110 | """ 111 | Compute self-similarity features 112 | 113 | :param channels: shrunk channels for self-similarity features 114 | :param smp_loc: shrunk sample locations (None for all) 115 | :return: self-similarity features 116 | """ 117 | 118 | shrink = self.options["shrink"] 119 | p_size = self.options["p_size"] / shrink 120 | n_r, n_c, n_ch = channels.shape 121 | 122 | ss_ftr = view_as_windows(channels, (p_size, p_size, n_ch)) 123 | 124 | if smp_loc is not None: 125 | ss_ftr = ss_ftr.reshape((n_r - p_size + 1, n_c - p_size + 1, 126 | p_size ** 2, n_ch)) 127 | r_pos = [r - p_size / 2 for r, _ in smp_loc] 128 | c_pos = [c - p_size / 2 for _, c in smp_loc] 129 | ss_ftr = ss_ftr[r_pos, c_pos] 130 | else: 131 | ss_ftr = ss_ftr.reshape((-1, p_size ** 2, n_ch)) 132 | 133 | n_cell = self.options["n_cell"] 134 | half_cell_size = int(round(p_size / (2.0 * n_cell))) 135 | grid_pos = [int(round((i + 1) * (p_size + 2 * half_cell_size - 1) / \ 136 | (n_cell + 1.0) - half_cell_size)) 137 | for i in xrange(n_cell)] 138 | grid_pos = [r * p_size + c for r in grid_pos for c in grid_pos] 139 | ss_ftr = ss_ftr[:, grid_pos] 140 | 141 | ss_ftr = pdist(ss_ftr) 142 | return ss_ftr.reshape((ss_ftr.shape[0], -1)) 143 | 144 | def get_features(self, src, smp_loc): 145 | bottom, right = (4 - src.shape[0] % 4) % 4, (4 - src.shape[1] % 4) % 4 146 | src = cv2.copyMakeBorder(src, 0, bottom, 0, right, 147 | borderType=cv2.BORDER_REFLECT) 148 | 149 | reg_ch, ss_ch = self.get_shrunk_channels(src) 150 | smp_loc = self.get_shrunk_loc(smp_loc) 151 | 152 | reg_ftr = self.get_reg_ftr(reg_ch, smp_loc) 153 | ss_ftr = self.get_ss_ftr(ss_ch, smp_loc) 154 | 155 | return reg_ftr, ss_ftr -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2017, Artanis 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | StructuredForests 2 | ================= 3 | 4 | ## Version 1.1 5 | 6 | Updates: 7 | * Use compression to reduce model size. 8 | * Rewrite the histogram function by Cython to accelerate detection. 9 | * Finetune some parameters to slightly improve accuracy. 10 | 11 | It seems the libjpeg package installed by Anaconda has some bugs in decoding images. The decoding result is different 12 | from the one outputted by Matlab's imread. Thus if you used Anaconda, you may consider uninstalling libjpeg and 13 | re-installing it by apt-get (for Ubuntu). 14 | 15 | 16 | ## Version 1.0 17 | 18 | A Python Implementation for Piotr's ICCV Paper "Structured Forests for Fast Edge Detection". The performance is almost 19 | the same as Piotr's original (Matlab) implementation (On BSDS500, Piotr's: \[ODS=0.738, OIS=0.758, AP=0.795, R50=0.923\], 20 | mine: \[ODS=0.739, OIS=0.759, AP=0.796, R50=0.924\]). 21 | 22 | For the original implementation, please check the author's webpage: 23 | http://research.microsoft.com/en-us/um/people/larryz/publications.htm 24 | . 25 | 26 | 27 | ### How to use 28 | * Platform: 29 | Ubuntu 14.04 + Anaconda is highly recommended. 30 | Nevertheless, I don't use any platform-dependent API. Hence the codes should work on Windows / Mac OS X as well. 31 | The only problem is related to Cython: it may not be easy to setup 64-bit Cython correctly on Windows, according to 32 | my previous experience ╮(╯▽╰)╭. 33 | 34 | 35 | * Toy Demo: 36 | A very small dataset "toy" was provided. You can run the code via "python StructureForests.py". This 37 | command will do the following things: 38 | * Extract 20,000 features from the training data, and save them in "model/data"; 39 | * Train 8 decision trees on the above features, and save them in "model/trees"; 40 | * Merge trees to build the final model, and save it in "model/forests". 41 | * Use the trained model to detect edges for the testing data, and save them in "edges". 42 | * **Note: Currently a model trained on the BSDS500 dataset is provided. If you don't remove it, only the last step of 43 | the above will be executed. Just for reference, on my machine the performance on "toy" is \[ODS=0.771, OIS=0.781, 44 | AP=0.843, R50=0.936\], if this model is used.** 45 | 46 | 47 | * Actual Usage: 48 | * You can use the provided model for prediction. If you want to train the model by yourself, remove the provided 49 | model and keep reading. 50 | * Download the BSDS500 dataset from http://www.eecs.berkeley.edu/Research/Projects/CS/vision/grouping/, 51 | and uncompress it. As a result, a directory named "BSR" is obtained, containing BSDS500, bench, and documentation. 52 | * Modify the bottom two lines in "StructuredForests.py" to: 53 | `model.train(bsds500_train("BSR"))` and `bsds500_test("BSR", "edges")`. That is, use the "BSR" dataset 54 | as input, instead of the "toy" dataset. 55 | * Also modify the model parameters, i.e., "n_pos" and "n_neg" in "StructuredForests.py" to 500,000. 56 | That is, use 1,000,000 features in total for training (the same as Piotr's ICCV paper). 57 | * Train and test the model via "python StructuredForests.py". On my machine, about 12 GB memory is required 58 | for training. 59 | 60 | 61 | ### What is missing 62 | * Multi-scale detection. However, implementing it should only require several lines of codes. 63 | * Speed. I didn't strive for speed. The current implementation is slower than the author's Matlab 64 | version, since only one thread is used, and there is no stochastic optimization like SSE. 65 | Nonetheless, the speed is acceptable: for BSDS500, detection requires about 1.0s per testing image; 66 | training requires about 11 hours. 67 | * Depth images. I never tried the NYU depth dataset. 68 | 69 | 70 | ### License 71 | I took some C++ codes from Piotr's original implementation. Those codes are licensed as the author required (see the 72 | author's webpage). 73 | 74 | For other codes, they are licensed under the BSD License. 75 | -------------------------------------------------------------------------------- /RandomForests.py: -------------------------------------------------------------------------------- 1 | __author__ = 'artanis' 2 | 3 | import math 4 | import numpy as np 5 | 6 | import pyximport 7 | pyximport.install(build_dir=".pyxbld") 8 | from _RandomForests import find_threshold 9 | 10 | 11 | class RandomForests(object): 12 | def __init__(self, n_tree=1, n_class=None, sub_n=None, sub_f=None, 13 | split='gini', min_count=1, min_child=1, max_depth=64, 14 | discretize=None, rand=np.random.RandomState(123)): 15 | """ 16 | m - number of trees, i.e., n_tree 17 | n - number of inputs 18 | f - number of features 19 | 20 | :param n_tree: [1] number of trees to train 21 | :param n_class: [max(labels)] number of classes 22 | :param sub_n: [5*n/m] number of data points for training each tree 23 | :param sub_f: [sqrt(f)] number features to sample for each node split 24 | :param split: ['gini'] options include 'gini', 'entropy' and 'twoing' 25 | :param min_count: [1] minimum number of data points to allow split 26 | :param min_child: [1] minimum number of data points allowed at child nodes 27 | :param max_depth: [64] maximum depth of tree 28 | :param discretize: optional function mapping structured to class labels 29 | format: [class, best] = discretize(structured, n_class) 30 | :param rand: [RandomState(123)] random number generator 31 | """ 32 | 33 | self.n_tree = n_tree 34 | self.n_class = n_class 35 | self.sub_n = sub_n 36 | self.sub_f = sub_f 37 | self.split = split 38 | self.min_count = min_count 39 | self.min_child = min_child 40 | self.max_depth = max_depth 41 | self.discretize = discretize 42 | self.rand = rand 43 | 44 | def train(self, ftrs, lbls): 45 | """ 46 | :param ftrs: features 47 | :param lbls: labels 48 | :return: a list of trees 49 | """ 50 | 51 | assert ftrs.shape[0] == lbls.shape[0] 52 | assert lbls.ndim == 1 or (lbls.dtype == np.int32 and 53 | self.discretize is not None and self.n_class is not None) 54 | ftrs = ftrs.astype(np.float32, copy=False) 55 | 56 | m = self.n_tree 57 | n, f = ftrs.shape 58 | min_child = max(1, self.min_child) 59 | min_count = max(1, self.min_count, self.min_child) 60 | n_class = np.max(lbls) + 1 if self.n_class is None else self.n_class 61 | sub_n = min(n, int(round(5.0 * n / m)) if self.sub_n is None else self.sub_n) 62 | sub_f = min(f, int(round(math.sqrt(f))) if self.sub_f is None else self.sub_f) 63 | split = ['gini', 'entropy', 'twoing'].index(self.split) 64 | forest = [] 65 | 66 | # train M random trees on different subsets of data 67 | for i in xrange(m): 68 | if n == sub_n: 69 | data, hs = ftrs, lbls 70 | else: 71 | idx = self.rand.permutation(n)[:sub_n] 72 | data, hs = ftrs[idx], lbls[idx] 73 | 74 | tree = self._train_tree(data, hs, n_class, sub_f, min_count, min_child, 75 | self.max_depth, split, self.discretize) 76 | forest.append(tree) 77 | 78 | return forest 79 | 80 | def _train_tree(self, ftrs, lbls, n_class, sub_f, min_count, min_child, 81 | max_depth, split, discretize): 82 | n, f = ftrs.shape 83 | max_n_node = 2 * n - 1 84 | 85 | thrs = np.zeros(max_n_node, dtype=ftrs.dtype) 86 | preds = np.zeros((max_n_node,) + lbls.shape[1:], dtype=lbls.dtype) 87 | probs = np.zeros((max_n_node, n_class), dtype=np.float64) 88 | fids = np.zeros(max_n_node, dtype=np.int32) 89 | cids = np.zeros(max_n_node, dtype=np.int32) 90 | counts = np.zeros(max_n_node, dtype=np.int32) 91 | depths = np.zeros(max_n_node, dtype=np.int32) 92 | dwts = np.ones(n, dtype=np.float32) / n 93 | dids = [None] * max_n_node 94 | 95 | dids[0] = np.arange(n) 96 | cid, max_cid = 0, 1 97 | while cid < max_cid: 98 | # get node data and store distribution 99 | sub_dids = dids[cid] 100 | sub_ftrs = ftrs[sub_dids] 101 | sub_lbls = lbls[sub_dids] 102 | sub_dwts = dwts[sub_dids] 103 | sub_n = sub_ftrs.shape[0] 104 | counts[cid] = sub_n 105 | dids[cid] = None 106 | 107 | if discretize is not None: 108 | sub_lbls, preds[cid] = discretize(sub_lbls, n_class) 109 | sub_lbls = sub_lbls.astype(np.int32, copy=False) 110 | 111 | assert np.all(0 <= sub_lbls) and np.all(sub_lbls < n_class) 112 | 113 | pure = np.all(sub_lbls[0] == sub_lbls) 114 | 115 | if discretize is None: 116 | if pure: 117 | probs[cid, sub_lbls[0]] = 1 118 | preds[cid] = sub_lbls[0] 119 | else: 120 | probs[cid] = np.histogram(sub_lbls, np.arange(n_class + 1), 121 | density=True)[0] 122 | preds[cid] = np.argmax(probs[cid]) 123 | 124 | # if pure node or insufficient data don't train split 125 | if pure or sub_n <= min_count or depths[cid] > max_depth: 126 | cid += 1 127 | continue 128 | 129 | # train split and continue 130 | sub_fids = np.arange(f) if f <= sub_f else self.rand.permutation(f)[:sub_f] 131 | split_fid, thr, gain = find_threshold( 132 | n_class, split, sub_ftrs[:, sub_fids], sub_lbls, sub_dwts) 133 | split_fid = sub_fids[split_fid] 134 | left = sub_ftrs[:, split_fid].flatten() < thr 135 | n_left = np.count_nonzero(left) 136 | if gain > 1e-10 and n_left >= min_child and (sub_n - n_left) >= min_child: 137 | thrs[cid] = thr 138 | fids[cid] = split_fid 139 | cids[cid] = max_cid + 1 140 | depths[max_cid: max_cid + 2] = depths[cid] + 1 141 | dids[max_cid: max_cid + 2] = sub_dids[left], sub_dids[~left] 142 | max_cid += 2 143 | cid += 1 144 | 145 | ids = np.arange(max_cid) 146 | return thrs[ids], probs[ids], preds[ids], fids[ids], cids[ids], \ 147 | counts[ids], depths[ids] -------------------------------------------------------------------------------- /RobustPCA.py: -------------------------------------------------------------------------------- 1 | __author__ = 'artanis' 2 | 3 | import numpy as np 4 | from scipy.linalg import svd 5 | 6 | 7 | def robust_pca(X, k, rand=np.random.RandomState(123)): 8 | """ 9 | Robust principal components analysis 10 | 11 | :param X: n x d, treated as n d-dimensional elements 12 | :param k: number of components to keep 13 | :param rand: [RandomState(123)] random number generator 14 | :return: 15 | Y: n x k, X after dimensionality reduction 16 | P: d x k, each column is a principal component 17 | mu: d, mean of X 18 | """ 19 | 20 | n, d = X.shape 21 | X = X.astype(np.float64, copy=False) 22 | eps = 1e-6 23 | 24 | if n == 1: 25 | U = np.zeros((d, k), dtype=np.float64) 26 | mu = X.flatten() 27 | return np.zeros((1, k), dtype=np.float64), U, mu 28 | else: 29 | mu = np.mean(X, axis=0) 30 | X -= mu 31 | 32 | # make sure X not too large or SVD slow O(min(d,n)^2.5) 33 | m = 2500 34 | if min(d, n) > m: 35 | X = X[rand.permutation(n)[:m]] 36 | n = m 37 | 38 | # get principal components using the SVD of X: X = U * S * V^T 39 | if d > n: 40 | U, S, _ = _robust_svd(np.dot(X, X.T) / (n - 1), rand=rand) 41 | s = [1.0 / np.sqrt(item) if abs(item) > eps else 0.0 for item in S] 42 | U = np.dot(np.dot(X.T, U), np.diag(s)) / np.sqrt(n - 1) 43 | else: 44 | U, S, _ = _robust_svd(np.dot(X.T, X) / (n - 1), rand=rand) 45 | 46 | # discard low variance principal components 47 | U = U[:, S > eps] 48 | U = U[:, :k] 49 | 50 | # perform dimensionality reduction 51 | P = np.zeros((d, k), dtype=np.float64) 52 | P[:, :U.shape[1]] = U 53 | return np.dot(X, P), P, mu 54 | 55 | 56 | def _robust_svd(X, trials=100, rand=np.random.RandomState(123)): 57 | # Robust version of SVD more likely to always converge 58 | 59 | try: 60 | U, S, V = svd(X, full_matrices=False) 61 | except Exception as e: 62 | if trials <= 0: 63 | raise e 64 | else: 65 | size = X.size 66 | idx = rand.random_integers(low=0, high=size-1) 67 | X[idx / X.shape[1], idx % X.shape[1]] += np.spacing(1) 68 | U, S, V = _robust_svd(X, trials - 1, rand) 69 | 70 | return U, S, V 71 | 72 | -------------------------------------------------------------------------------- /StructuredForests.py: -------------------------------------------------------------------------------- 1 | __author__ = 'artanis' 2 | 3 | import os 4 | import sys 5 | import tables 6 | import cv2 7 | import numpy as N 8 | from math import floor, ceil, log 9 | from scipy.ndimage.morphology import distance_transform_edt 10 | from BaseStructuredForests import BaseStructuredForests 11 | from RandomForests import RandomForests 12 | from RobustPCA import robust_pca 13 | from utils import conv_tri, gradient 14 | 15 | import pyximport 16 | pyximport.install(build_dir=".pyxbld", 17 | setup_args={"include_dirs": N.get_include()}) 18 | from _StructuredForests import predict_core, non_maximum_supr 19 | 20 | 21 | class StructuredForests(BaseStructuredForests): 22 | def __init__(self, options, model_dir="model/", 23 | rand=N.random.RandomState(123)): 24 | """ 25 | :param options: 26 | rgbd: 0 for RGB, 1 for RGB + depth 27 | shrink: amount to shrink channels 28 | n_orient: number of orientations per gradient scale 29 | grd_smooth_rad: radius for image gradient smoothing 30 | grd_norm_rad: radius for gradient normalization 31 | reg_smooth_rad: radius for reg channel smoothing 32 | ss_smooth_rad: radius for sim channel smoothing 33 | p_size: size of image patches 34 | g_size: size of ground truth patches 35 | n_cell: number of self similarity cells 36 | 37 | n_pos: number of positive patches per tree 38 | n_neg: number of negative patches per tree 39 | fraction: fraction of features to use to train each tree 40 | n_tree: number of trees in forest to train 41 | n_class: number of classes (clusters) for binary splits 42 | min_count: minimum number of data points to allow split 43 | min_child: minimum number of data points allowed at child nodes 44 | max_depth: maximum depth of tree 45 | split: options include 'gini', 'entropy' and 'twoing' 46 | discretize: optional function mapping structured to class labels 47 | 48 | stride: stride at which to compute edges 49 | sharpen: sharpening amount (can only decrease after training) 50 | n_tree_eval: number of trees to evaluate per location 51 | nms: if true apply non-maximum suppression to edges 52 | 53 | :param model_dir: directory for model 54 | A trained model will contain 55 | thrs: threshold corresponding to each feature index 56 | fids: feature indices for each node 57 | cids: indices of children for each node 58 | edge_bnds: begin / end of edge points for each node 59 | edge_pts: edge points for each node 60 | n_seg: number of segmentations for each node 61 | segs: segmentation map for each node 62 | 63 | :param rand: random number generator 64 | """ 65 | 66 | BaseStructuredForests.__init__(self, options) 67 | assert self.options["g_size"] % 2 == 0 68 | assert self.options["stride"] % self.options["shrink"] == 0 69 | 70 | self.model_dir = model_dir 71 | self.data_dir = os.path.join(self.model_dir, "data") 72 | self.tree_dir = os.path.join(self.model_dir, "trees") 73 | self.forest_dir = os.path.join(self.model_dir, "forests") 74 | self.data_prefix = "data_" 75 | self.tree_prefix = "tree_" 76 | self.forest_name = "forest.h5" 77 | self.comp_filt = tables.Filters(complib="zlib", complevel=1) 78 | 79 | self.trained = False 80 | 81 | try: 82 | self.load_model() 83 | except: 84 | self.model = {} 85 | print >> sys.stderr, "No model file found. Training is required." 86 | 87 | self.rand = rand 88 | 89 | def load_model(self): 90 | model_file = os.path.join(self.forest_dir, self.forest_name) 91 | 92 | with tables.open_file(model_file, filters=self.comp_filt) as mfile: 93 | self.model = { 94 | "thrs": mfile.get_node("/thrs")[:], 95 | "fids": mfile.get_node("/fids")[:], 96 | "cids": mfile.get_node("/cids")[:], 97 | "edge_bnds": mfile.get_node("/edge_bnds")[:].flatten(), 98 | "edge_pts": mfile.get_node("/edge_pts")[:].flatten(), 99 | "n_seg": mfile.get_node("/n_seg")[:].flatten(), 100 | "segs": mfile.get_node("/segs")[:], 101 | } 102 | 103 | self.trained = True 104 | 105 | return self.model 106 | 107 | def predict(self, src): 108 | stride = self.options["stride"] 109 | sharpen = self.options["sharpen"] 110 | shrink = self.options["shrink"] 111 | p_size = self.options["p_size"] 112 | g_size = self.options["g_size"] 113 | n_cell = self.options["n_cell"] 114 | n_tree_eval = self.options["n_tree_eval"] 115 | nms = self.options["nms"] if "nms" in self.options else False 116 | thrs = self.model["thrs"] 117 | fids = self.model["fids"] 118 | cids = self.model["cids"] 119 | edge_bnds = self.model["edge_bnds"] 120 | edge_pts = self.model["edge_pts"] 121 | n_seg = self.model["n_seg"] 122 | segs = self.model["segs"] 123 | p_rad = p_size / 2 124 | g_rad = g_size / 2 125 | 126 | pad = cv2.copyMakeBorder(src, p_rad, p_rad, p_rad, p_rad, 127 | borderType=cv2.BORDER_REFLECT) 128 | 129 | reg_ch, ss_ch = self.get_shrunk_channels(pad) 130 | 131 | if sharpen != 0: 132 | pad = conv_tri(pad, 1) 133 | 134 | dst = predict_core(pad, reg_ch, ss_ch, shrink, p_size, g_size, n_cell, 135 | stride, sharpen, n_tree_eval, thrs, fids, cids, 136 | n_seg, segs, edge_bnds, edge_pts) 137 | 138 | if sharpen == 0: 139 | alpha = 2.1 * stride ** 2 / g_size ** 2 / n_tree_eval 140 | elif sharpen == 1: 141 | alpha = 1.8 * stride ** 2 / g_size ** 2 / n_tree_eval 142 | else: 143 | alpha = 1.65 * stride ** 2 / g_size ** 2 / n_tree_eval 144 | 145 | dst = N.minimum(dst * alpha, 1.0) 146 | dst = conv_tri(dst, 1)[g_rad: src.shape[0] + g_rad, 147 | g_rad: src.shape[1] + g_rad] 148 | 149 | if nms: 150 | dy, dx = N.gradient(conv_tri(dst, 4)) 151 | _, dxx = N.gradient(dx) 152 | dyy, dxy = N.gradient(dy) 153 | orientation = N.arctan2(dyy * N.sign(-dxy) + 1e-5, dxx) 154 | orientation[orientation < 0] += N.pi 155 | 156 | dst = non_maximum_supr(dst, orientation, 1, 5, 1.02) 157 | 158 | return dst 159 | 160 | def train(self, input_data): 161 | if self.trained: 162 | print >> sys.stderr, "Model has been trained. Quit training." 163 | return 164 | 165 | self.prepare_data(input_data) 166 | self.train_tree() 167 | self.merge_trees() 168 | self.load_model() 169 | 170 | def prepare_data(self, input_data): 171 | """ 172 | Prepare data for model training 173 | """ 174 | 175 | n_img = len(input_data) 176 | 177 | if not os.path.exists(self.data_dir): 178 | os.makedirs(self.data_dir) 179 | 180 | n_tree = self.options["n_tree"] 181 | n_pos = self.options["n_pos"] 182 | n_neg = self.options["n_neg"] 183 | fraction = self.options["fraction"] 184 | p_size = self.options["p_size"] 185 | g_size = self.options["g_size"] 186 | shrink = self.options["shrink"] 187 | p_rad, g_rad = p_size / 2, g_size / 2 188 | n_ftr_dim = N.sum(self.get_ftr_dim()) 189 | n_smp_ftr_dim = int(n_ftr_dim * fraction) 190 | rand = self.rand 191 | 192 | for i in xrange(n_tree): 193 | data_file = self.data_prefix + str(i + 1) + ".h5" 194 | data_path = os.path.join(self.data_dir, data_file) 195 | if os.path.exists(data_path): 196 | print "Found Data %d '%s', reusing..." % ((i + 1), data_file) 197 | continue 198 | 199 | ftrs = N.zeros((n_pos + n_neg, n_smp_ftr_dim), dtype=N.float32) 200 | lbls = N.zeros((n_pos + n_neg, g_size, g_size), dtype=N.int32) 201 | sids = rand.permutation(n_ftr_dim)[:n_smp_ftr_dim] 202 | total = 0 203 | 204 | for j, (img, bnds, segs) in enumerate(input_data): 205 | mask = N.zeros(bnds[0].shape, dtype=bnds[0].dtype) 206 | mask[::shrink, ::shrink] = 1 207 | mask[:p_rad] = mask[-p_rad:] = 0 208 | mask[:, :p_rad] = mask[:, -p_rad:] = 0 209 | 210 | n_pos_per_gt = int(ceil(float(n_pos) / n_img / len(bnds))) 211 | n_neg_per_gt = int(ceil(float(n_neg) / n_img / len(bnds))) 212 | 213 | for k, boundary in enumerate(bnds): 214 | dis = distance_transform_edt(boundary == 0) 215 | 216 | pos_loc = ((dis < g_rad) * mask).nonzero() 217 | pos_loc = zip(pos_loc[0].tolist(), pos_loc[1].tolist()) 218 | pos_loc = [pos_loc[item] for item in 219 | rand.permutation(len(pos_loc))[:n_pos_per_gt]] 220 | 221 | neg_loc = ((dis >= g_rad) * mask).nonzero() 222 | neg_loc = zip(neg_loc[0].tolist(), neg_loc[1].tolist()) 223 | neg_loc = [neg_loc[item] for item in 224 | rand.permutation(len(neg_loc))[:n_neg_per_gt]] 225 | 226 | loc = pos_loc + neg_loc 227 | n_loc = min(len(loc), ftrs.shape[0] - total) 228 | loc = [loc[item] for item in rand.permutation(len(loc))[:n_loc]] 229 | if n_loc == 0: 230 | continue 231 | 232 | ftr = N.concatenate(self.get_features(img, loc), axis=1) 233 | assert ftr.shape[1] == n_ftr_dim 234 | ftr = ftr[:, sids] 235 | 236 | lbl = N.zeros((ftr.shape[0], g_size, g_size), dtype=N.int8) 237 | for m, (x, y) in enumerate(loc): 238 | sub = segs[k][x - g_rad: x + g_rad, y - g_rad: y + g_rad] 239 | sub = N.unique(sub, return_inverse=True)[1] 240 | lbl[m] = sub.reshape((g_size, g_size)) 241 | 242 | ftrs[total: total + n_loc] = ftr 243 | lbls[total: total + n_loc] = lbl 244 | total += n_loc 245 | 246 | sys.stdout.write("Processing Data %d: %d/%d\r" % (i + 1, j + 1, n_img)) 247 | sys.stdout.flush() 248 | print 249 | 250 | with tables.open_file(data_path, "w", filters=self.comp_filt) as dfile: 251 | dfile.create_carray("/", "ftrs", obj=ftrs[:total]) 252 | dfile.create_carray("/", "lbls", obj=lbls[:total]) 253 | dfile.create_carray("/", "sids", obj=sids.astype(N.int32)) 254 | print "Saving %d samples to '%s'..." % (total, data_file) 255 | 256 | def train_tree(self): 257 | """ 258 | Train a single tree 259 | """ 260 | 261 | n_tree = self.options["n_tree"] 262 | 263 | if not os.path.exists(self.tree_dir): 264 | os.makedirs(self.tree_dir) 265 | 266 | rf = RandomForests(n_class=self.options["n_class"], 267 | min_count=self.options["min_count"], 268 | min_child=self.options["min_child"], 269 | max_depth=self.options["max_depth"], 270 | split=self.options["split"], 271 | discretize=self.options["discretize"], 272 | rand=self.rand) 273 | 274 | for i in xrange(n_tree): 275 | data_file = self.data_prefix + str(i + 1) + ".h5" 276 | data_path = os.path.join(self.data_dir, data_file) 277 | tree_file = self.tree_prefix + str(i + 1) + ".h5" 278 | tree_path = os.path.join(self.tree_dir, tree_file) 279 | if os.path.exists(tree_path): 280 | print "Found Tree %d '%s', reusing..." % ((i + 1), tree_file) 281 | continue 282 | 283 | with tables.open_file(data_path, filters=self.comp_filt) as dfile: 284 | ftrs = dfile.get_node("/ftrs")[:] 285 | lbls = dfile.get_node("/lbls")[:] 286 | sids = dfile.get_node("/sids")[:] 287 | 288 | forest = rf.train(ftrs, lbls) 289 | thrs, probs, preds, fids, cids, counts, depths = forest[0] 290 | fids[cids > 0] = sids[fids[cids > 0]] 291 | 292 | with tables.open_file(tree_path, "w", filters=self.comp_filt) as tfile: 293 | tfile.create_carray("/", "fids", obj=fids) 294 | tfile.create_carray("/", "thrs", obj=thrs) 295 | tfile.create_carray("/", "cids", obj=cids) 296 | tfile.create_carray("/", "probs", obj=probs) 297 | tfile.create_carray("/", "segs", obj=preds) 298 | tfile.create_carray("/", "counts", obj=counts) 299 | tfile.create_carray("/", "depths", obj=depths) 300 | tfile.close() 301 | 302 | sys.stdout.write("Processing Tree %d/%d\r" % (i + 1, n_tree)) 303 | sys.stdout.flush() 304 | print 305 | 306 | def merge_trees(self): 307 | """ 308 | Accumulate trees and merge into final model 309 | """ 310 | 311 | n_tree = self.options["n_tree"] 312 | g_size = self.options["g_size"] 313 | 314 | if not os.path.exists(self.forest_dir): 315 | os.makedirs(self.forest_dir) 316 | 317 | forest_path = os.path.join(self.forest_dir, self.forest_name) 318 | if os.path.exists(forest_path): 319 | print "Found model, reusing..." 320 | return 321 | 322 | trees = [] 323 | for i in xrange(n_tree): 324 | tree_file = self.tree_prefix + str(i + 1) + ".h5" 325 | tree_path = os.path.join(self.tree_dir, tree_file) 326 | 327 | with tables.open_file(tree_path, filters=self.comp_filt) as mfile: 328 | tree = {"fids": mfile.get_node("/fids")[:], 329 | "thrs": mfile.get_node("/thrs")[:], 330 | "cids": mfile.get_node("/cids")[:], 331 | "segs": mfile.get_node("/segs")[:]} 332 | trees.append(tree) 333 | 334 | max_n_node = 0 335 | for i in xrange(n_tree): 336 | max_n_node = max(max_n_node, trees[i]["fids"].shape[0]) 337 | 338 | # merge all fields of all trees 339 | thrs = N.zeros((n_tree, max_n_node), dtype=N.float64) 340 | fids = N.zeros((n_tree, max_n_node), dtype=N.int32) 341 | cids = N.zeros((n_tree, max_n_node), dtype=N.int32) 342 | segs = N.zeros((n_tree, max_n_node, g_size, g_size), dtype=N.int32) 343 | for i in xrange(n_tree): 344 | tree = trees[i] 345 | n_node = tree["fids"].shape[0] 346 | thrs[i, :n_node] = tree["thrs"].flatten() 347 | fids[i, :n_node] = tree["fids"].flatten() 348 | cids[i, :n_node] = tree["cids"].flatten() 349 | segs[i, :n_node] = tree["segs"] 350 | 351 | # remove very small segments (<=5 pixels) 352 | n_seg = N.max(segs.reshape((n_tree, max_n_node, g_size ** 2)), axis=2) + 1 353 | for i in xrange(n_tree): 354 | for j in xrange(max_n_node): 355 | m = n_seg[i, j] 356 | if m <= 1: 357 | continue 358 | 359 | S = segs[i, j] 360 | remove = False 361 | 362 | for k in xrange(m): 363 | Sk = (S == k) 364 | if N.count_nonzero(Sk) > 5: 365 | continue 366 | 367 | S[Sk] = N.median(S[conv_tri(Sk.astype(N.float64), 1) > 0]) 368 | remove = True 369 | 370 | if remove: 371 | S = N.unique(S, return_inverse=True)[1] 372 | segs[i, j] = S.reshape((g_size, g_size)) 373 | n_seg[i, j] = N.max(S) + 1 374 | 375 | # store compact representations of sparse binary edge patches 376 | n_bnd = self.options["sharpen"] + 1 377 | edge_pts = [] 378 | edge_bnds = N.zeros((n_tree, max_n_node, n_bnd), dtype=N.int32) 379 | for i in xrange(n_tree): 380 | for j in xrange(max_n_node): 381 | if cids[i, j] != 0 or n_seg[i, j] <= 1: 382 | continue 383 | 384 | E = gradient(segs[i, j].astype(N.float64))[0] > 0.01 385 | E0 = 0 386 | 387 | for k in xrange(n_bnd): 388 | r, c = N.nonzero(E & (~ E0)) 389 | edge_pts += [r[m] * g_size + c[m] for m in xrange(len(r))] 390 | edge_bnds[i, j, k] = len(r) 391 | 392 | E0 = E 393 | E = conv_tri(E.astype(N.float64), 1) > 0.01 394 | 395 | segs = segs.reshape((-1, segs.shape[-2], segs.shape[-1])) 396 | edge_pts = N.asarray(edge_pts, dtype=N.int32) 397 | edge_bnds = N.hstack(([0], N.cumsum(edge_bnds.flatten()))).astype(N.int32) 398 | 399 | with tables.open_file(forest_path, "w", filters=self.comp_filt) as mfile: 400 | mfile.create_carray("/", "thrs", obj=thrs) 401 | mfile.create_carray("/", "fids", obj=fids) 402 | mfile.create_carray("/", "cids", obj=cids) 403 | mfile.create_carray("/", "edge_bnds", obj=edge_bnds) 404 | mfile.create_carray("/", "edge_pts", obj=edge_pts) 405 | mfile.create_carray("/", "n_seg", obj=n_seg) 406 | mfile.create_carray("/", "segs", obj=segs) 407 | mfile.close() 408 | 409 | 410 | def discretize(segs, n_class, n_sample, rand): 411 | """ 412 | Convert a set of segmentations into a set of labels in [0, n_class - 1] 413 | 414 | :param segs: segmentations 415 | :param n_class: number of classes (clusters) for binary splits 416 | :param n_sample: number of samples for clustering structured labels 417 | :param rand: random number generator 418 | """ 419 | 420 | w = segs[0].shape[0] 421 | segs = segs.reshape((segs.shape[0], w ** 2)) 422 | 423 | # compute all possible lookup inds for w x w patches 424 | ids = N.arange(w ** 4, dtype=N.float64) 425 | ids1 = N.floor(ids / w / w) 426 | ids2 = ids - ids1 * w * w 427 | kp = ids2 > ids1 428 | ids1 = ids1[kp] 429 | ids2 = ids2[kp] 430 | 431 | # compute n binary codes zs of length nSamples 432 | n_sample = min(n_sample, ids1.shape[0]) 433 | kp = rand.permutation(ids1.shape[0])[:n_sample] 434 | n = segs.shape[0] 435 | ids1 = ids1[kp].astype(N.int32) 436 | ids2 = ids2[kp].astype(N.int32) 437 | 438 | zs = N.zeros((n, n_sample), dtype=N.float64) 439 | for i in xrange(n): 440 | zs[i] = (segs[i][ids1] == segs[i][ids2]) 441 | zs -= N.mean(zs, axis=0) 442 | zs = zs[:, N.any(zs, axis=0)] 443 | 444 | if N.count_nonzero(zs) == 0: 445 | lbls = N.ones(n, dtype=N.int32) 446 | segs = segs[0] 447 | else: 448 | # find most representative segs (closest to mean) 449 | ind = N.argmin(N.sum(zs * zs, axis=1)) 450 | segs = segs[ind] 451 | 452 | # discretize zs by discretizing pca dimensions 453 | d = min(5, n_sample, int(floor(log(n_class, 2)))) 454 | zs = robust_pca(zs, d, rand=rand)[0] 455 | lbls = N.zeros(n, dtype=N.int32) 456 | for i in xrange(d): 457 | lbls += (zs[:, i] < 0).astype(N.int32) * 2 ** i 458 | lbls = N.unique(lbls, return_inverse=True)[1].astype(N.int32) 459 | 460 | return lbls, segs.reshape((-1, w, w)) 461 | 462 | 463 | def bsds500_train(input_root): 464 | import scipy.io as SIO 465 | from skimage import img_as_float 466 | from skimage.io import imread 467 | 468 | dataset_dir = os.path.join(input_root, "BSDS500", "data") 469 | image_dir = os.path.join(dataset_dir, "images", "train") 470 | label_dir = os.path.join(dataset_dir, "groundTruth", "train") 471 | data = [] 472 | 473 | for file_name in os.listdir(label_dir): 474 | gts = SIO.loadmat(os.path.join(label_dir, file_name)) 475 | gts = gts["groundTruth"].flatten() 476 | bnds = [gt["Boundaries"][0, 0] for gt in gts] 477 | segs = [gt["Segmentation"][0, 0] for gt in gts] 478 | 479 | img = imread(os.path.join(image_dir, file_name[:-3] + "jpg")) 480 | img = img_as_float(img) 481 | 482 | data.append((img, bnds, segs)) 483 | 484 | return data 485 | 486 | 487 | def bsds500_test(model, input_root, output_root): 488 | from skimage import img_as_float, img_as_ubyte 489 | from skimage.io import imread, imsave 490 | 491 | if not os.path.exists(output_root): 492 | os.makedirs(output_root) 493 | 494 | image_dir = os.path.join(input_root, "BSDS500", "data", "images", "test") 495 | file_names = filter(lambda name: name[-3:] == "jpg", os.listdir(image_dir)) 496 | n_image = len(file_names) 497 | 498 | for i, file_name in enumerate(file_names): 499 | img = img_as_float(imread(os.path.join(image_dir, file_name))) 500 | 501 | edge = img_as_ubyte(model.predict(img)) 502 | 503 | imsave(os.path.join(output_root, file_name[:-3] + "png"), edge) 504 | 505 | sys.stdout.write("Processing Image %d/%d\r" % (i + 1, n_image)) 506 | sys.stdout.flush() 507 | print 508 | 509 | 510 | if __name__ == "__main__": 511 | rand = N.random.RandomState(1) 512 | 513 | options = { 514 | "rgbd": 0, 515 | "shrink": 2, 516 | "n_orient": 4, 517 | "grd_smooth_rad": 0, 518 | "grd_norm_rad": 4, 519 | "reg_smooth_rad": 2, 520 | "ss_smooth_rad": 8, 521 | "p_size": 32, 522 | "g_size": 16, 523 | "n_cell": 5, 524 | 525 | "n_pos": 10000, 526 | "n_neg": 10000, 527 | "fraction": 0.25, 528 | "n_tree": 8, 529 | "n_class": 2, 530 | "min_count": 1, 531 | "min_child": 8, 532 | "max_depth": 64, 533 | "split": "gini", 534 | "discretize": lambda lbls, n_class: 535 | discretize(lbls, n_class, n_sample=256, rand=rand), 536 | 537 | "stride": 2, 538 | "sharpen": 2, 539 | "n_tree_eval": 4, 540 | "nms": True, 541 | } 542 | 543 | model = StructuredForests(options, rand=rand) 544 | model.train(bsds500_train("toy")) 545 | bsds500_test(model, "toy", "edges") 546 | -------------------------------------------------------------------------------- /_RandomForests.pyx: -------------------------------------------------------------------------------- 1 | __author__ = 'artanis' 2 | 3 | import numpy as np 4 | cimport numpy as np 5 | 6 | ctypedef np.int32_t INT32 7 | ctypedef np.float32_t FLOAT32 8 | 9 | 10 | cdef extern from "_random_forests.h": 11 | void forestFindThr(int H, int N, int F, const FLOAT32 *data, const INT32 *hs, 12 | const float* ws, const INT32 *order, const int split, 13 | int &fid, float &thr, double &gain) except + 14 | 15 | 16 | def find_threshold(H, split, ftrs, lbls, dwts): 17 | cdef int N = ftrs.shape[0], F = ftrs.shape[1] 18 | cdef np.ndarray[FLOAT32, ndim=2] data 19 | cdef np.ndarray[INT32, ndim=1] hs 20 | cdef np.ndarray[FLOAT32, ndim=1] ws 21 | cdef np.ndarray[INT32, ndim=2] order 22 | 23 | data = np.asfortranarray(ftrs, dtype=np.float32) 24 | hs = np.asfortranarray(lbls, dtype=np.int32) 25 | ws = np.asfortranarray(dwts, dtype=np.float32) 26 | order = np.asfortranarray(np.argsort(ftrs, axis=0, kind='mergesort'), 27 | dtype=np.int32) 28 | 29 | cdef int fid = -1 30 | cdef float thr = 0 31 | cdef double gain = 0 32 | 33 | forestFindThr(H, N, F, &data[0, 0], &hs[0], &ws[0], &order[0, 0], 34 | split, fid, thr, gain) 35 | 36 | return fid, thr, gain -------------------------------------------------------------------------------- /_RandomForests.pyxbld: -------------------------------------------------------------------------------- 1 | def make_ext(modname, pyxfilename): 2 | import numpy 3 | from distutils.extension import Extension 4 | 5 | return Extension(name=modname, 6 | sources=[pyxfilename, "_random_forests.cpp"], 7 | language="c++", 8 | libraries=["stdc++"], 9 | include_dirs=[numpy.get_include(), "."], 10 | extra_compile_args=["-std=c++11"]) -------------------------------------------------------------------------------- /_StructuredForests.pyx: -------------------------------------------------------------------------------- 1 | __author__ = 'artanis' 2 | 3 | import math 4 | import numpy as N 5 | 6 | cimport numpy as N 7 | 8 | 9 | ctypedef N.int32_t C_INT32 10 | ctypedef N.float64_t C_FLOAT64 11 | 12 | 13 | def build_feature_table(shrink, p_size, n_cell, n_ch): 14 | p_size /= shrink 15 | 16 | reg_tb = [] 17 | for i in xrange(p_size): 18 | for j in xrange(p_size): 19 | for k in xrange(n_ch): 20 | reg_tb.append([i, j, k]) 21 | 22 | half_cell_size = int(round(p_size / (2.0 * n_cell))) 23 | grid_pos = [int(round((i + 1) * (p_size + 2 * half_cell_size - 1) / \ 24 | (n_cell + 1.0) - half_cell_size)) 25 | for i in xrange(n_cell)] 26 | grid_pos = [(r, c) for r in grid_pos for c in grid_pos] 27 | 28 | ss_tb = [] 29 | for i in xrange(n_cell ** 2): 30 | for j in xrange(i + 1, n_cell ** 2): 31 | for z in xrange(n_ch): 32 | x1, y1 = grid_pos[i] 33 | x2, y2 = grid_pos[j] 34 | ss_tb.append([x1, y1, x2, y2, z]) 35 | 36 | return N.asarray(reg_tb, dtype=N.int32), \ 37 | N.asarray(ss_tb, dtype=N.int32) 38 | 39 | 40 | def find_leaves(double[:, :, :] src, double[:, :, :] reg_ch, 41 | double[:, :, :] ss_ch, 42 | int shrink, int p_size, int g_size, int n_cell, int stride, 43 | int n_tree_eval, 44 | double[:, :] thrs, int[:, :] fids, int[:, :] cids): 45 | cdef int n_ftr_ch = reg_ch.shape[2] 46 | cdef int height = src.shape[0] - p_size, width = src.shape[1] - p_size 47 | cdef int n_tree = cids.shape[0], n_node_per_tree = cids.shape[1] 48 | cdef int n_reg_dim = (p_size / shrink) ** 2 * n_ftr_ch 49 | cdef int i, j, k, x1, x2, y1, y2, z, tree_idx, node_idx, ftr_idx 50 | cdef double ftr 51 | cdef int[:, :] reg_tb, ss_tb 52 | cdef N.ndarray[C_INT32, ndim=3] lids_arr 53 | 54 | reg_tb, ss_tb = build_feature_table(shrink, p_size, n_cell, n_ftr_ch) 55 | 56 | lids_arr = N.zeros((src.shape[0], src.shape[1], n_tree_eval), dtype=N.int32) 57 | cdef int[:, :, :] lids = lids_arr 58 | 59 | with nogil: 60 | for i from 0 <= i < height by stride: 61 | for j from 0 <= j < width by stride: 62 | for k from 0 <= k < n_tree_eval: 63 | tree_idx = ((i + j) / stride % 2 * n_tree_eval + k) % n_tree 64 | node_idx = 0 65 | 66 | while cids[tree_idx, node_idx] != 0: 67 | ftr_idx = fids[tree_idx, node_idx] 68 | 69 | if ftr_idx >= n_reg_dim: 70 | x1 = ss_tb[ftr_idx - n_reg_dim, 0] + i / shrink 71 | y1 = ss_tb[ftr_idx - n_reg_dim, 1] + j / shrink 72 | x2 = ss_tb[ftr_idx - n_reg_dim, 2] + i / shrink 73 | y2 = ss_tb[ftr_idx - n_reg_dim, 3] + j / shrink 74 | z = ss_tb[ftr_idx - n_reg_dim, 4] 75 | 76 | ftr = ss_ch[x1, y1, z] - ss_ch[x2, y2, z] 77 | else: 78 | x1 = reg_tb[ftr_idx, 0] + i / shrink 79 | y1 = reg_tb[ftr_idx, 1] + j / shrink 80 | z = reg_tb[ftr_idx, 2] 81 | 82 | ftr = reg_ch[x1, y1, z] 83 | 84 | if ftr < thrs[tree_idx, node_idx]: 85 | node_idx = cids[tree_idx, node_idx] - 1 86 | else: 87 | node_idx = cids[tree_idx, node_idx] 88 | 89 | lids[i, j, k] = tree_idx * n_node_per_tree + node_idx 90 | 91 | return lids_arr 92 | 93 | 94 | def build_neigh_table(g_size): 95 | tb = N.zeros((g_size, g_size, 4, 2), dtype=N.int32) 96 | dir_x = N.asarray([1, 1, -1, -1], dtype=N.int32) 97 | dir_y = N.asarray([1, -1, 1, -1], dtype=N.int32) 98 | 99 | for i in xrange(g_size): 100 | for j in xrange(g_size): 101 | for k in xrange(4): 102 | r = min(max(dir_x[k] + i, 0), g_size - 1) 103 | c = min(max(dir_y[k] + j, 0), g_size - 1) 104 | tb[i, j, k] = [r, c] 105 | 106 | return tb 107 | 108 | 109 | def compose(double[:, :, :] src, int[:, :, :] lids, 110 | int p_size, int g_size, int stride, int sharpen, int n_tree_eval, 111 | int[:, :] cids, int[:] n_seg, int[:, :, :] segs, int[:] edge_bnds, 112 | int[:] edge_pts): 113 | cdef int height = src.shape[0] - p_size, width = src.shape[1] - p_size 114 | cdef int depth = src.shape[2], border = (p_size - g_size) / 2 115 | cdef int n_bnd = edge_bnds.shape[0] / cids.shape[0] / cids.shape[1] 116 | cdef int n_s, max_n_s = N.max(n_seg) 117 | cdef int i, j, k, m, n, p, begin, end 118 | cdef int leaf_idx, x1, x2, y1, y2, best_seg 119 | cdef double err, min_err 120 | cdef N.ndarray[C_FLOAT64, ndim=2] dst_arr 121 | 122 | cdef int[:, :] patch = N.zeros((g_size, g_size), dtype=N.int32) 123 | cdef double[:] count = N.zeros((max_n_s,), dtype=N.float64), 124 | cdef double[:, :] mean = N.zeros((max_n_s, depth), dtype=N.float64) 125 | cdef int[:, :, :, :] neigh_tb = build_neigh_table(g_size) 126 | 127 | dst_arr = N.zeros((src.shape[0], src.shape[1]), dtype=N.float64) 128 | cdef double[:, :] dst = dst_arr 129 | 130 | with nogil: 131 | for i from 0 <= i < height by stride: 132 | for j from 0 <= j < width by stride: 133 | for k from 0 <= k < n_tree_eval: 134 | leaf_idx = lids[i, j, k] 135 | 136 | begin = edge_bnds[leaf_idx * n_bnd] 137 | end = edge_bnds[leaf_idx * n_bnd + sharpen + 1] 138 | if begin == end: 139 | continue 140 | 141 | n_s = n_seg[leaf_idx] 142 | if n_s == 1: 143 | continue 144 | 145 | patch[:, :] = segs[leaf_idx] 146 | count[:] = 0.0 147 | mean[:] = 0.0 148 | 149 | # compute color model for each segment using every other pixel 150 | for m from 0 <= m < g_size by 2: 151 | for n from 0 <= n < g_size by 2: 152 | count[patch[m, n]] += 1.0 153 | 154 | for p from 0 <= p < depth: 155 | mean[patch[m, n], p] += \ 156 | src[i + m + border, j + n + border, p] 157 | 158 | for m from 0 <= m < n_s: 159 | for n from 0 <= n < depth: 160 | mean[m, n] /= count[m] 161 | 162 | # update segment according to local color values 163 | end = edge_bnds[leaf_idx * n_bnd + sharpen] 164 | for m from begin <= m < end: 165 | min_err = 1e10 166 | best_seg = -1 167 | 168 | x1 = edge_pts[m] / g_size 169 | y1 = edge_pts[m] % g_size 170 | 171 | for n from 0 <= n < 4: 172 | x2 = neigh_tb[x1, y1, n, 0] 173 | y2 = neigh_tb[x1, y1, n, 1] 174 | 175 | if patch[x2, y2] == best_seg: 176 | continue 177 | 178 | err = 0.0 179 | for p from 0 <= p < depth: 180 | err += (src[x1 + i + border, y1 + j + border, p] - 181 | mean[patch[x2, y2], p]) ** 2 182 | 183 | if err < min_err: 184 | min_err = err 185 | best_seg = patch[x2, y2] 186 | 187 | patch[x1, y1] = best_seg 188 | 189 | # convert mask to edge maps (examining expanded set of pixels) 190 | end = edge_bnds[leaf_idx * n_bnd + sharpen + 1] 191 | for m from begin <= m < end: 192 | x1 = edge_pts[m] / g_size 193 | y1 = edge_pts[m] % g_size 194 | 195 | for n from 0 <= n < 4: 196 | x2 = neigh_tb[x1, y1, n, 0] 197 | y2 = neigh_tb[x1, y1, n, 1] 198 | 199 | if patch[x1, y1] != patch[x2, y2]: 200 | dst[x1 + i, y1 + j] += 1.0 201 | break 202 | 203 | return dst_arr 204 | 205 | 206 | def predict_core(N.ndarray[C_FLOAT64, ndim=3] src, 207 | N.ndarray[C_FLOAT64, ndim=3] reg_ch, 208 | N.ndarray[C_FLOAT64, ndim=3] ss_ch, 209 | int shrink, int p_size, int g_size, int n_cell, 210 | int stride, int sharpen, int n_tree_eval, 211 | N.ndarray[C_FLOAT64, ndim=2] thrs, 212 | N.ndarray[C_INT32, ndim=2] fids, 213 | N.ndarray[C_INT32, ndim=2] cids, 214 | N.ndarray[C_INT32, ndim=1] n_seg, 215 | N.ndarray[C_INT32, ndim=3] segs, 216 | N.ndarray[C_INT32, ndim=1] edge_bnds, 217 | N.ndarray[C_INT32, ndim=1] edge_pts): 218 | cdef int n_tree = cids.shape[0], n_node_per_tree = cids.shape[1] 219 | cdef int n_bnd = edge_bnds.shape[0] / n_tree / n_node_per_tree 220 | cdef int i, j, k, m, begin, end 221 | cdef int leaf_idx, loc, x1, y1 222 | cdef N.ndarray[C_INT32, ndim=3] lids 223 | cdef N.ndarray[C_FLOAT64, ndim=2] dst 224 | 225 | lids = find_leaves(src, reg_ch, ss_ch, shrink, p_size, g_size, n_cell, 226 | stride, n_tree_eval, thrs, fids, cids) 227 | 228 | if sharpen == 0: 229 | dst = N.zeros((src.shape[0], src.shape[1]), dtype=N.float64) 230 | 231 | for i in xrange(0, src.shape[0] - p_size, stride): 232 | for j in xrange(0, src.shape[1] - p_size, stride): 233 | for k in xrange(n_tree_eval): 234 | leaf_idx = lids[i, j, k] 235 | 236 | begin = edge_bnds[leaf_idx * n_bnd] 237 | end = edge_bnds[leaf_idx * n_bnd + 1] 238 | if begin == end: 239 | continue 240 | 241 | for m in xrange(begin, end): 242 | loc = edge_pts[m] 243 | x1 = loc / g_size + i 244 | y1 = loc % g_size + j 245 | 246 | dst[x1, y1] += 1.0 247 | else: 248 | dst = compose(src, lids, p_size, g_size, stride, sharpen, n_tree_eval, 249 | cids, n_seg, segs, edge_bnds, edge_pts) 250 | 251 | return dst 252 | 253 | 254 | cdef inline float bilinear_interp(double[:, :] img, float x, float y) nogil: 255 | """ 256 | Return img[y, x] via bilinear interpolation 257 | """ 258 | 259 | cdef int h = img.shape[0], w = img.shape[1] 260 | 261 | if x < 0: 262 | x = 0 263 | elif x > w - 1.001: 264 | x = w - 1.001 265 | 266 | if y < 0: 267 | y = 0 268 | elif y > h - 1.001: 269 | y = h - 1.001 270 | 271 | cdef int x0 = int(x), y0 = int(y), x1 = x0 + 1, y1 = y0 + 1 272 | cdef double dx0 = x - x0, dy0 = y - y0, dx1 = 1 - dx0, dy1 = 1 - dy0 273 | 274 | return img[y0, x0] * dx1 * dy1 + img[y0, x1] * dx0 * dy1 + \ 275 | img[y1, x0] * dx1 * dy0 + img[y1, x1] * dx0 * dy0 276 | 277 | 278 | def non_maximum_supr(double[:, :] E0, double[:, :] O, int r, int s, double m): 279 | """ 280 | Non-Maximum Suppression 281 | 282 | :param E0: original edge map 283 | :param O: orientation map 284 | :param r: radius for nms suppression 285 | :param s: radius for suppress boundaries 286 | :param m: multiplier for conservative suppression 287 | :return: suppressed edge map 288 | """ 289 | 290 | cdef int h = E0.shape[0], w = E0.shape[1], x, y, d 291 | cdef double e, e0, co, si 292 | cdef N.ndarray[C_FLOAT64, ndim=2] E_arr = N.zeros((h, w), dtype=N.float64) 293 | cdef double[:, :] E = E_arr 294 | cdef double[:, :] C = N.cos(O), S = N.sin(O) 295 | 296 | with nogil: 297 | # suppress edges where edge is stronger in orthogonal direction 298 | for y from 0 <= y < h: 299 | for x from 0 <= x < w: 300 | e = E[y, x] = E0[y, x] 301 | if e == 0: 302 | continue 303 | 304 | e *= m 305 | co = C[y, x] 306 | si = S[y, x] 307 | 308 | for d from -r <= d <= r: 309 | if d != 0: 310 | e0 = bilinear_interp(E0, x + d * co, y + d * si) 311 | if e < e0: 312 | E[y, x] = 0 313 | break 314 | 315 | # suppress noisy edge estimates near boundaries 316 | s = w / 2 if s > w / 2 else s 317 | s = h / 2 if s > h / 2 else s 318 | 319 | for x from 0 <= x < s: 320 | for y from 0 <= y < h: 321 | E[y, x] *= x / s 322 | E[y, w - 1 - x] *= x / s 323 | 324 | for x from 0 <= x < w: 325 | for y from 0 <= y < s: 326 | E[y, x] *= y / s 327 | E[h - 1 - y, x] *= y / s 328 | 329 | return E_arr -------------------------------------------------------------------------------- /_random_forests.cpp: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Piotr's Computer Vision Matlab Toolbox Version 3.24 3 | * Copyright 2014 Piotr Dollar. [pdollar-at-gmail.com] 4 | * Licensed under the Simplified BSD License [see external/bsd.txt] 5 | *******************************************************************************/ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | using namespace std; 12 | 13 | #include "_random_forests.h" 14 | 15 | #define gini(p) p*p 16 | #define entropy(p) (-p*flog2(float(p))) 17 | 18 | 19 | // fast approximate log2(x) from Paul Mineiro 20 | inline float flog2( float x ) { 21 | union { float f; uint32_t i; } vx = { x }; 22 | union { uint32_t i; float f; } mx = { (vx.i & 0x007FFFFF) | 0x3f000000 }; 23 | float y = float(vx.i); y *= 1.1920928955078125e-7f; 24 | return y - 124.22551499f - 1.498030302f * mx.f 25 | - 1.72587999f / (0.3520887068f + mx.f); 26 | } 27 | 28 | // perform actual computation 29 | void forestFindThr(int H, int N, int F, const float *data, const int *hs, 30 | const float* ws, const int *order, const int split, 31 | int &fid, float &thr, double &gain) 32 | { 33 | double *Wl, *Wr, *W; float *data1; int *order1; 34 | int i, j, j1, j2, h; double vBst, vInit, v, w, wl, wr, g, gl, gr; 35 | Wl=new double[H]; Wr=new double[H]; W=new double[H]; 36 | // perform initialization 37 | vBst = vInit = 0; g = 0; w = 0; fid = 0; thr = 0; 38 | for( i=0; i>0] 62 | j1=order1[j]; j2=order1[j+1]; h=hs[j1]; 63 | wl+=ws[j1]; Wl[h]+=ws[j1]; wr-=ws[j1]; Wr[h]-=ws[j1]; 64 | g=0; for( int h1=0; h1=1e-6f ) { 68 | vBst=v; fid=i; thr=0.5f*(data1[j1]+data1[j2]); } 69 | } 70 | } 71 | delete [] Wl; delete [] Wr; delete [] W; gain = vInit-vBst; 72 | } -------------------------------------------------------------------------------- /_random_forests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void forestFindThr(int H, int N, int F, const float *data, const int *hs, 4 | const float* ws, const int *order, const int split, 5 | int &fid, float &thr, double &gain); -------------------------------------------------------------------------------- /_utils.pyx: -------------------------------------------------------------------------------- 1 | import numpy as N 2 | cimport numpy as N 3 | 4 | ctypedef N.float32_t FLOAT32 5 | ctypedef N.float64_t FLOAT64 6 | 7 | 8 | def histogram_core(double[:, :] magnitude, double[:, :] orientation, 9 | int downscale, int n_orient, int interp): 10 | cdef int n_row = magnitude.shape[0], n_col = magnitude.shape[1] 11 | cdef int n_rbin = (n_row + downscale - 1) / downscale 12 | cdef int n_cbin = (n_col + downscale - 1) / downscale 13 | cdef int i, j, r, c, o1, o2 14 | cdef double o_range = N.pi / n_orient, o 15 | cdef N.ndarray[FLOAT64, ndim=3] hist_arr 16 | 17 | hist_arr = N.zeros((n_rbin, n_cbin, n_orient), dtype=N.float64) 18 | cdef double[:, :, :] hist = hist_arr 19 | 20 | with nogil: 21 | for i from 0 <= i < n_row: 22 | for j from 0 <= j < n_col: 23 | r, c = i / downscale, j / downscale 24 | 25 | if interp: 26 | o = orientation[i, j] / o_range 27 | o1 = o % n_orient 28 | o2 = (o1 + 1) % n_orient 29 | hist[r, c, o1] += magnitude[i, j] * (1 + o - o) 30 | hist[r, c, o2] += magnitude[i, j] * (o - o) 31 | else: 32 | o1 = (orientation[i, j] / o_range + 0.5) % n_orient 33 | hist[r, c, o1] += magnitude[i, j] 34 | 35 | return hist_arr / downscale ** 2 36 | 37 | 38 | def pdist_core(double[:, :, :] src): 39 | cdef int n_in = src.shape[0], n_pt = src.shape[1], n_dim = src.shape[2] 40 | cdef int i, j, k, m, n 41 | cdef N.ndarray[FLOAT64, ndim=3] dst_arr 42 | 43 | dst_arr = N.zeros((src.shape[0], n_pt * (n_pt - 1) / 2, n_dim), 44 | dtype=N.float64) 45 | cdef double[:, :, :] dst = dst_arr 46 | 47 | with nogil: 48 | for i from 0 <= i < n_in: 49 | n = 0 50 | 51 | for j from 0 <= j < n_pt: 52 | for k from j + 1 <= k < n_pt: 53 | for m from 0 <= m < n_dim: 54 | dst[i, n, m] = src[i, j, m] - src[i, k, m] 55 | 56 | n += 1 57 | 58 | return dst_arr -------------------------------------------------------------------------------- /model/forests/forest.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtanisCV/StructuredForests/1154ed021e86714579f9921a44e400839487de88/model/forests/forest.h5 -------------------------------------------------------------------------------- /toy/BSDS500/data/groundTruth/test/16068.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtanisCV/StructuredForests/1154ed021e86714579f9921a44e400839487de88/toy/BSDS500/data/groundTruth/test/16068.mat -------------------------------------------------------------------------------- /toy/BSDS500/data/groundTruth/test/196062.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtanisCV/StructuredForests/1154ed021e86714579f9921a44e400839487de88/toy/BSDS500/data/groundTruth/test/196062.mat -------------------------------------------------------------------------------- /toy/BSDS500/data/groundTruth/test/296028.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtanisCV/StructuredForests/1154ed021e86714579f9921a44e400839487de88/toy/BSDS500/data/groundTruth/test/296028.mat -------------------------------------------------------------------------------- /toy/BSDS500/data/groundTruth/test/335094.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtanisCV/StructuredForests/1154ed021e86714579f9921a44e400839487de88/toy/BSDS500/data/groundTruth/test/335094.mat -------------------------------------------------------------------------------- /toy/BSDS500/data/groundTruth/train/12003.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtanisCV/StructuredForests/1154ed021e86714579f9921a44e400839487de88/toy/BSDS500/data/groundTruth/train/12003.mat -------------------------------------------------------------------------------- /toy/BSDS500/data/groundTruth/train/2092.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtanisCV/StructuredForests/1154ed021e86714579f9921a44e400839487de88/toy/BSDS500/data/groundTruth/train/2092.mat -------------------------------------------------------------------------------- /toy/BSDS500/data/groundTruth/train/8049.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtanisCV/StructuredForests/1154ed021e86714579f9921a44e400839487de88/toy/BSDS500/data/groundTruth/train/8049.mat -------------------------------------------------------------------------------- /toy/BSDS500/data/groundTruth/train/8143.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtanisCV/StructuredForests/1154ed021e86714579f9921a44e400839487de88/toy/BSDS500/data/groundTruth/train/8143.mat -------------------------------------------------------------------------------- /toy/BSDS500/data/images/test/16068.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtanisCV/StructuredForests/1154ed021e86714579f9921a44e400839487de88/toy/BSDS500/data/images/test/16068.jpg -------------------------------------------------------------------------------- /toy/BSDS500/data/images/test/196062.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtanisCV/StructuredForests/1154ed021e86714579f9921a44e400839487de88/toy/BSDS500/data/images/test/196062.jpg -------------------------------------------------------------------------------- /toy/BSDS500/data/images/test/296028.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtanisCV/StructuredForests/1154ed021e86714579f9921a44e400839487de88/toy/BSDS500/data/images/test/296028.jpg -------------------------------------------------------------------------------- /toy/BSDS500/data/images/test/335094.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtanisCV/StructuredForests/1154ed021e86714579f9921a44e400839487de88/toy/BSDS500/data/images/test/335094.jpg -------------------------------------------------------------------------------- /toy/BSDS500/data/images/train/12003.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtanisCV/StructuredForests/1154ed021e86714579f9921a44e400839487de88/toy/BSDS500/data/images/train/12003.jpg -------------------------------------------------------------------------------- /toy/BSDS500/data/images/train/2092.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtanisCV/StructuredForests/1154ed021e86714579f9921a44e400839487de88/toy/BSDS500/data/images/train/2092.jpg -------------------------------------------------------------------------------- /toy/BSDS500/data/images/train/8049.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtanisCV/StructuredForests/1154ed021e86714579f9921a44e400839487de88/toy/BSDS500/data/images/train/8049.jpg -------------------------------------------------------------------------------- /toy/BSDS500/data/images/train/8143.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtanisCV/StructuredForests/1154ed021e86714579f9921a44e400839487de88/toy/BSDS500/data/images/train/8143.jpg -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | __author__ = 'artanis' 2 | 3 | import cv2 4 | import numpy as N 5 | 6 | import pyximport 7 | pyximport.install(build_dir=".pyxbld", 8 | setup_args={"include_dirs": N.get_include()}) 9 | from _utils import histogram_core, pdist_core 10 | 11 | 12 | def resize(src, size): 13 | assert len(size) == 2 14 | size = (int(size[0]), int(size[1])) 15 | 16 | if size == src.shape[:2]: 17 | return src 18 | elif size[0] < src.shape[0] and size[1] < src.shape[1]: 19 | return cv2.resize(src, size[::-1], interpolation=cv2.INTER_AREA) 20 | else: 21 | return cv2.resize(src, size[::-1], interpolation=cv2.INTER_LINEAR) 22 | 23 | 24 | def conv_tri(src, radius): 25 | """ 26 | Image convolution with a triangle filter. 27 | 28 | :param src: input image 29 | :param radius: gradient normalization radius 30 | :return: convolution result 31 | """ 32 | 33 | if radius == 0: 34 | return src 35 | elif radius <= 1: 36 | p = 12.0 / radius / (radius + 2) - 2 37 | kernel = N.asarray([1, p, 1], dtype=N.float64) / (p + 2) 38 | return cv2.sepFilter2D(src, ddepth=-1, kernelX=kernel, kernelY=kernel, 39 | borderType=cv2.BORDER_REFLECT) 40 | else: 41 | radius = int(radius) 42 | kernel = range(1, radius + 1) + [radius + 1] + range(radius, 0, -1) 43 | kernel = N.asarray(kernel, dtype=N.float64) / (radius + 1) ** 2 44 | return cv2.sepFilter2D(src, ddepth=-1, kernelX=kernel, kernelY=kernel, 45 | borderType=cv2.BORDER_REFLECT) 46 | 47 | 48 | def rgb2luv(src): 49 | """ 50 | This function implements rgb to luv conversion in a way similar to UCSD 51 | computer vision toolbox. 52 | """ 53 | 54 | assert src.dtype == N.float64 or src.dtype == N.float32 55 | assert src.ndim == 3 and src.shape[-1] == 3 56 | 57 | a = 29.0 ** 3 / 27 58 | y0 = 8.0 / a 59 | maxi = 1.0 / 270 60 | 61 | table = [i / 1024.0 for i in xrange(1025)] 62 | table = [116 * y ** (1.0 / 3.0) - 16 if y > y0 else y * a for y in table] 63 | table = [l * maxi for l in table] 64 | table += [table[-1]] * 39 65 | 66 | rgb2xyz_mat = N.asarray([[0.430574, 0.222015, 0.020183], 67 | [0.341550, 0.706655, 0.129553], 68 | [0.178325, 0.071330, 0.939180]]) 69 | xyz = N.dot(src, rgb2xyz_mat) 70 | nz = 1.0 / (xyz[:, :, 0] + 15 * xyz[:, :, 1] + 3 * xyz[:, :, 2] + 1e-35) 71 | 72 | L = [table[int(1024 * item)] for item in xyz[:, :, 1].flatten()] 73 | L = N.asarray(L).reshape(xyz.shape[:2]) 74 | u = L * (13 * 4 * xyz[:, :, 0] * nz - 13 * 0.197833) + 88 * maxi 75 | v = L * (13 * 9 * xyz[:, :, 1] * nz - 13 * 0.468331) + 134 * maxi 76 | 77 | luv = N.concatenate((L[:, :, None], u[:, :, None], v[:, :, None]), axis=2) 78 | return luv.astype(src.dtype, copy=False) 79 | 80 | 81 | def gradient(src, norm_radius=0, norm_const=0.01): 82 | """ 83 | Compute gradient magnitude and orientation at each image location. 84 | 85 | :param src: input image 86 | :param norm_radius: normalization radius (no normalization if 0) 87 | :param norm_const: normalization constant 88 | :return: gradient magnitude and orientation (0 ~ pi) 89 | """ 90 | 91 | if src.ndim == 2: 92 | src = src[:, :, None] 93 | 94 | dx = N.zeros(src.shape, dtype=src.dtype) 95 | dy = N.zeros(src.shape, dtype=src.dtype) 96 | for i in xrange(src.shape[2]): 97 | dy[:, :, i], dx[:, :, i] = N.gradient(src[:, :, i]) 98 | 99 | magnitude = N.sqrt(dx ** 2 + dy ** 2) 100 | idx_2 = N.argmax(magnitude, axis=2) 101 | idx_0, idx_1 = N.indices(magnitude.shape[:2]) 102 | magnitude = magnitude[idx_0, idx_1, idx_2] 103 | if norm_radius != 0: 104 | magnitude /= conv_tri(magnitude, norm_radius) + norm_const 105 | magnitude = magnitude.astype(src.dtype, copy=False) 106 | 107 | dx = dx[idx_0, idx_1, idx_2] 108 | dy = dy[idx_0, idx_1, idx_2] 109 | orientation = N.arctan2(dy, dx) 110 | orientation[orientation < 0] += N.pi 111 | orientation[N.abs(dx) + N.abs(dy) < 1e-5] = 0.5 * N.pi 112 | orientation = orientation.astype(src.dtype, copy=False) 113 | 114 | return magnitude, orientation 115 | 116 | 117 | def histogram(magnitude, orientation, downscale, n_orient, interp=False): 118 | """ 119 | Compute oriented gradient histograms. 120 | 121 | :param magnitude: gradient magnitude 122 | :param orientation: gradient orientation 123 | :param downscale: spatially downscaling factor 124 | :param n_orient: number of orientation bins 125 | :param interp: true for interpolation over orientations 126 | :return: oriented gradient histogram 127 | """ 128 | 129 | dtype = magnitude.dtype 130 | magnitude = magnitude.astype(N.float64, copy=False) 131 | orientation = orientation.astype(N.float64, copy=False) 132 | 133 | hist = histogram_core(magnitude, orientation, downscale, n_orient, interp) 134 | return hist.astype(dtype, copy=False) 135 | 136 | 137 | def pdist(points): 138 | """ 139 | Compute the pairwise differences between n-dimensional points in a way 140 | specified in the paper "Structured Forests for Fast Edge Detection". 141 | Note: Indeed this is not a valid distance measurement (asymmetry). 142 | 143 | :param points: n-dimensional points 144 | :return: pairwise differences 145 | """ 146 | 147 | dtype = points.dtype 148 | prefix_shape, (n_pt, n_dim) = points.shape[:-2], points.shape[-2:] 149 | src = points.reshape((-1, n_pt, n_dim)).astype(N.float64, copy=False) 150 | 151 | dst = pdist_core(src).astype(dtype, copy=False) 152 | return dst.reshape(prefix_shape + (n_pt * (n_pt - 1) / 2, n_dim)) --------------------------------------------------------------------------------