├── .gitignore ├── README.md ├── classifiers └── gaussian_mixture_1d_em.py └── computer-vision ├── binary_dilation_erosion.py ├── canny.py ├── crosscorrelation_convolution.py ├── eigenfaces.py ├── gauss.py ├── gradients.py ├── graph_image_segmentation.py ├── harris.py ├── histogram_equalization.py ├── hough.py ├── images └── faces │ ├── 0000.jpg │ ├── 0001.jpg │ ├── 0002.jpg │ ├── 0003.jpg │ ├── 0004.jpg │ ├── 0005.jpg │ ├── 0006.jpg │ ├── 0007.jpg │ ├── 0008.jpg │ ├── 0009.jpg │ ├── 0010.jpg │ ├── 0011.jpg │ ├── 0012.jpg │ ├── 0013.jpg │ ├── 0014.jpg │ ├── 0015.jpg │ ├── 0016.jpg │ ├── 0017.jpg │ ├── 0018.jpg │ ├── 0019.jpg │ ├── 0020.jpg │ ├── 0021.jpg │ ├── 0022.jpg │ ├── 0023.jpg │ ├── 0024.jpg │ ├── 0025.jpg │ ├── 0026.jpg │ ├── 0027.jpg │ ├── 0028.jpg │ ├── 0029.jpg │ ├── 0030.jpg │ ├── 0031.jpg │ ├── 0032.jpg │ ├── 0033.jpg │ ├── 0034.jpg │ ├── 0035.jpg │ ├── 0036.jpg │ ├── 0037.jpg │ ├── 0038.jpg │ ├── 0039.jpg │ ├── 0040.jpg │ ├── 0041.jpg │ ├── 0042.jpg │ ├── 0043.jpg │ ├── 0044.jpg │ ├── 0045.jpg │ ├── 0046.jpg │ ├── 0047.jpg │ ├── 0048.jpg │ ├── 0049.jpg │ ├── 0050.jpg │ ├── 0051.jpg │ ├── 0052.jpg │ ├── 0053.jpg │ ├── 0054.jpg │ ├── 0055.jpg │ ├── 0056.jpg │ ├── 0057.jpg │ ├── 0058.jpg │ ├── 0059.jpg │ ├── 0060.jpg │ ├── 0061.jpg │ ├── 0062.jpg │ ├── 0063.jpg │ ├── 0064.jpg │ ├── 0065.jpg │ ├── 0066.jpg │ ├── 0067.jpg │ ├── 0068.jpg │ ├── 0069.jpg │ ├── 0070.jpg │ ├── 0071.jpg │ ├── 0072.jpg │ ├── 0073.jpg │ ├── 0074.jpg │ ├── 0075.jpg │ ├── 0076.jpg │ ├── 0077.jpg │ ├── 0078.jpg │ ├── 0079.jpg │ ├── 0080.jpg │ ├── 0081.jpg │ ├── 0082.jpg │ ├── 0083.jpg │ ├── 0084.jpg │ ├── 0085.jpg │ ├── 0086.jpg │ ├── 0087.jpg │ ├── 0088.jpg │ ├── 0089.jpg │ ├── 0090.jpg │ ├── 0091.jpg │ ├── 0092.jpg │ ├── 0093.jpg │ ├── 0094.jpg │ ├── 0095.jpg │ ├── 0096.jpg │ ├── 0097.jpg │ ├── 0098.jpg │ └── 0099.jpg ├── mean_shift_segmentation.py ├── otsu.py ├── prewitt.py ├── rank_order.py ├── sift.py ├── sobel.py ├── template_matching.py └── util.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 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 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | #Ipython Notebook 62 | .ipynb_checkpoints 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | Custom implementations of common machine learning algorithms. 4 | Purely for training purposes and not intended for productive use. 5 | 6 | # Files 7 | 8 | All files can simply be executed with `python filename.py` (i.e. no command line arguments). 9 | 10 | **Computer Vision** 11 | 12 | * **binary_dilation_erosion.py** - Dilation, Erosion, Opening and Closing for binary images. 13 | * **canny.py** - Canny edge detector. 14 | * **crosscorrelation_convolution.py** - Implementation of crosscorrelation and convolution to apply filters to images. 15 | * **eigenfaces.py** - Apply a PCA (from sklearn) to human faces, plot the principal components (aka eigenfaces). 16 | * **gauss.py** - Gauss filter for smoothing of images. 17 | * **gradients.py** - Calculate x and y derivatives of an image using symmetric, backward and forward gradient techniques. 18 | * **graph_image_segmentation.py** - Split an image to segments using a simple graph based technique with internal and external subgraph differences. 19 | * **harris.py** - Calculate the Harris edge detector score of pixels in an image. 20 | * **histogram_equalization.py** - Normalize the intensity histogram of an image using histogram stretching and cumulative histogram equalization. 21 | * **hough.py** - Find a line in a noisy image using a Hough Transformation. 22 | * **mean_shift_segmentation.py** - Split an image to segments using mean shift clustering. 23 | * **otsu.py** - Binarize an image using Otsu's method. 24 | * **prewitt.py** - Apply a Prewitt filter to an image to calculate the x/y gradients. 25 | * **rank_order.py** - Apply rank order filters to an image for (non-binary) erosion/dilation/closing/opening, median filtering and morphological edge detection. 26 | * **sift.py** - Simplified implementation of the SIFT keypoint locator. Does not contain the descriptor nor sophisticated keypoint filtering (using hessian und principal curvatures). Also does not resize scales with higher sigmas. 27 | * **sobel.py** - Apply a Sobel filter to an image for smoothed gradient calculation. 28 | * **template_matching.py** - Find an example template image in a larger image. 29 | 30 | **Classifiers** 31 | 32 | * **gaussian_mixture_1d_em.py** - Train a mixture model of 1d gaussians using the EM algorithm. 33 | 34 | # Requirements 35 | 36 | Python 2.7, scipy, numpy, sklearn, scikit-image, matplotlib 37 | -------------------------------------------------------------------------------- /classifiers/gaussian_mixture_1d_em.py: -------------------------------------------------------------------------------- 1 | """1D Gaussian mixture model for random points solved with EM algorithm .""" 2 | from __future__ import division, print_function 3 | import numpy as np 4 | import random 5 | import matplotlib.pyplot as plt 6 | 7 | random.seed(44) 8 | 9 | K = 4 10 | NB_POINTS = 50 11 | NB_ITERATIONS = 100 12 | 13 | def main(): 14 | """ 15 | Generate random points and fit 1D gaussians to them. 16 | Basic formulas are from https://www.youtube.com/watch?v=iQoXFmbXRJA . 17 | """ 18 | # generate random points 19 | points_x = np.array([random.random() * 100 for _ in range(NB_POINTS)]) 20 | points = [(x, 0) for x in points_x] 21 | 22 | # initialize gaussians randomly 23 | gaussians = [(random.random()*100, random.random()*10) for _ in range(K)] 24 | print(gaussians) 25 | 26 | plot(points, gaussians) 27 | 28 | # perform EM for N iterations 29 | for _ in range(NB_ITERATIONS): 30 | gaussians = em_iteration(points_x, gaussians) 31 | print(gaussians) 32 | 33 | plot(points, gaussians) 34 | 35 | print("Finished") 36 | 37 | def em_iteration(points_x, gaussians): 38 | """Perform one iteration of EM given some points and gaussians.""" 39 | # Estimate for each point a and cluster/gaussian c the probability p(a|c) 40 | clustering = np.zeros((points_x.shape[0], len(gaussians))) 41 | for i in range(points_x.shape[0]): 42 | xi = points_x[i] 43 | p_xi_clusters = gauss_probs(xi, gaussians) 44 | total_prob = sum(p_xi_clusters) 45 | 46 | for k in range(len(gaussians)): 47 | p_xi_cluster = p_xi_clusters[k] 48 | p_cluster = 1 49 | p_cluster_xi = (p_xi_cluster * p_cluster) / total_prob 50 | clustering[i][k] = p_cluster_xi 51 | 52 | # sum up for each cluster/gaussian its total probability of all points 53 | cluster_prob_sums = np.sum(clustering, axis=0) 54 | 55 | # update mu/mean and sigma of each cluster/gaussian 56 | new_gaussians = [] 57 | for k in range(len(gaussians)): 58 | cluster = clustering[:, k] 59 | cluster_prob = cluster_prob_sums[k] 60 | new_mu = np.dot(cluster, points_x) / cluster_prob 61 | new_sig = np.sqrt(np.dot(cluster, (points_x - new_mu)**2) / cluster_prob) 62 | new_gaussians.append((new_mu, new_sig)) 63 | 64 | return new_gaussians 65 | 66 | def gauss_probs(xi, gaussians): 67 | """Estimate for a point a and gaussians C the probabilities p(a|c).""" 68 | probs = [] 69 | for mu, sig in gaussians: 70 | probs.append(gauss_prob(xi, mu, sig)) 71 | return probs 72 | 73 | def gauss_prob(xi, mu, sig): 74 | """For a single point a and a gaussian c estimate p(a|c).""" 75 | return gaussian(np.array([xi]), mu, sig)[0] 76 | 77 | def gaussian(x, mu, sig): 78 | """For a set of points X and a gaussian c estimate p(xi|c) (for each point).""" 79 | return (1/np.sqrt(2*np.pi*sig**2)) * np.exp((-(x - mu)**2) / (2 * sig**2)) 80 | 81 | def plot(points, gaussians): 82 | """Plot the example points and gaussians.""" 83 | plt.scatter([x for (x, y) in points], [y for (x, y) in points], color="black") 84 | 85 | for mu, sig in gaussians: 86 | plt.plot(gaussian(np.linspace(0, 100, 100), mu, sig)) 87 | 88 | plt.show() 89 | 90 | if __name__ == "__main__": 91 | main() 92 | -------------------------------------------------------------------------------- /computer-vision/binary_dilation_erosion.py: -------------------------------------------------------------------------------- 1 | """Apply binary (0/1) dilation, erosion, closing and opening to an example 2 | image.""" 3 | from __future__ import division, print_function 4 | from scipy import signal 5 | import numpy as np 6 | import random 7 | import util 8 | np.random.seed(42) 9 | random.seed(42) 10 | 11 | def main(): 12 | """Main function.""" 13 | img = np.zeros((128, 128), dtype=np.uint8) 14 | img[0, 0:15] = 255 15 | img[5:10, 5:10] = 255 16 | img[64:71, 64:71] = 255 17 | img[72:75, 72] = 255 18 | img[91:95, 24:29] = 255 19 | img[91:92, 29:31] = 255 20 | img[30:60, 40:80] = 255 21 | img[10:70, 100:102] = 255 22 | img[50:60, 98:100] = 255 23 | img[15, 20:100] = 255 24 | img[90:110, 80:120] = 255 25 | img[80:120, 90:110] = 255 26 | img[100:105, 100:105] = 0 27 | 28 | util.plot_images_grayscale( 29 | [img, dilation(img), erosion(img), opening(img), closing(img)], 30 | ["Binary Image", "Binary Dilation", "Binary Erosion", "Binary Opening", "Binary Closing"] 31 | ) 32 | 33 | def dilation(img): 34 | """Perform Dilation on an image. 35 | Args: 36 | img The image to be changed. 37 | Returns: 38 | Changed image 39 | """ 40 | img = np.copy(img).astype(np.float32) 41 | mask = np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]]) 42 | corr = signal.correlate(img, mask, mode="same") / 9.0 43 | corr[corr > 1e-8] = 255 44 | corr = corr.astype(np.uint8) 45 | return corr 46 | 47 | def erosion(img): 48 | """Perform Erosion on an image. 49 | Args: 50 | img The image to be changed. 51 | Returns: 52 | Changed image 53 | """ 54 | img = np.copy(img).astype(np.float32) 55 | mask = np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]]) 56 | corr = signal.correlate(img, mask, mode="same") / 9.0 57 | corr[corr < 255.0 - 1e-8] = 0 58 | corr = corr.astype(np.uint8) 59 | return corr 60 | 61 | def opening(img): 62 | """Perform Opening on an image. 63 | Args: 64 | img The image to be changed. 65 | Returns: 66 | Changed image 67 | """ 68 | return erosion(dilation(img)) 69 | 70 | def closing(img): 71 | """Perform Closing on an image. 72 | Args: 73 | img The image to be changed. 74 | Returns: 75 | Changed image 76 | """ 77 | return dilation(erosion(img)) 78 | 79 | if __name__ == "__main__": 80 | main() 81 | -------------------------------------------------------------------------------- /computer-vision/canny.py: -------------------------------------------------------------------------------- 1 | """Apply Canny Edge Detector to an image.""" 2 | from __future__ import division, print_function 3 | from scipy import signal 4 | from scipy.ndimage import filters as filters 5 | import numpy as np 6 | import random 7 | from skimage import data 8 | from skimage import util as skiutil 9 | from skimage import feature 10 | import util 11 | np.random.seed(42) 12 | random.seed(42) 13 | 14 | def main(): 15 | """Load image, apply Canny, plot.""" 16 | img = skiutil.img_as_float(data.camera()) 17 | img = filters.gaussian_filter(img, sigma=1.0) 18 | 19 | sobel_y = np.array([ 20 | [-1, -2, -1], 21 | [0, 0, 0], 22 | [1, 2, 1] 23 | ]) 24 | sobel_x = np.rot90(sobel_y) # rotates counter-clockwise 25 | 26 | img_sx = signal.correlate(img, sobel_x, mode="same") 27 | img_sy = signal.correlate(img, sobel_y, mode="same") 28 | 29 | g_magnitudes = np.sqrt(img_sx**2 + img_sy**2) / np.sqrt(2) 30 | g_orientations = np.arctan2(img_sy, img_sx) 31 | 32 | g_mag_nonmax = non_maximum_suppression(g_magnitudes, g_orientations) 33 | canny = hysteresis(g_mag_nonmax, 0.35, 0.05) 34 | 35 | ground_truth = feature.canny(data.camera()) 36 | 37 | util.plot_images_grayscale( 38 | [img, g_magnitudes, g_mag_nonmax, canny, ground_truth], 39 | ["Image", "After Sobel", "After nonmax", "Canny", "Canny (Ground Truth)"] 40 | ) 41 | 42 | def non_maximum_suppression(g_magnitudes, g_orientations): 43 | """Apply Non Maximum Suppression to the gradient of an image. 44 | Args: 45 | g_magnitudes Magnitude of the gradient of an image (in 2D). 46 | g_orientations Orientations of the gradient of an image (in 2D). 47 | Returns: 48 | Modified gradient magnitudes 49 | """ 50 | gm, go = g_magnitudes, g_orientations 51 | gm_out = np.copy(gm) 52 | height, width = gm.shape 53 | for y in range(height): 54 | for x in range(width): 55 | theta = np.degrees(go[y, x]) 56 | theta = 180 + theta if theta < 0 else theta 57 | theta = util.quantize(theta, [0, 45, 90, 135, 180]) 58 | 59 | north = gm[y-1, x] if y > 0 else 0 60 | south = gm[y+1, x] if y < height-1 else 0 61 | west = gm[y, x-1] if x > 0 else 0 62 | east = gm[y, x+1] if x < width-1 else 0 63 | northwest = gm[y-1, x-1] if y > 0 and x > 0 else 0 64 | northeast = gm[y-1, x+1] if y > 0 and x < width-1 else 0 65 | southwest = gm[y+1, x-1] if y < height-1 and x > 0 else 0 66 | southeast = gm[y+1, x+1] if y < height-1 and x < width-1 else 0 67 | 68 | if theta == 0 or theta == 180: 69 | gm_out[y, x] = gm[y, x] if gm[y, x] >= north and gm[y, x] >= south else 0 70 | elif theta == 45: 71 | gm_out[y, x] = gm[y, x] if gm[y, x] >= northwest and gm[y, x] >= southeast else 0 72 | elif theta == 90: 73 | gm_out[y, x] = gm[y, x] if gm[y, x] >= west and gm[y, x] >= east else 0 74 | else: # theta == 135 75 | gm_out[y, x] = gm[y, x] if gm[y, x] >= northeast and gm[y, x] >= southwest else 0 76 | 77 | return gm_out 78 | 79 | def hysteresis(g_magnitudes, threshold_high, threshold_low): 80 | """Applies histeresis thresholding to the gradient of an image. 81 | Args: 82 | g_magnitudes Magnitude of the gradient of an image (in 2D). 83 | threshold_high Upper/strong threshold. 84 | threshold_low Lower/weak threshold. 85 | Returns: 86 | Modified gradient magnitude. 87 | """ 88 | gm_strong = np.zeros(g_magnitudes.shape) 89 | gm_weak = np.zeros(g_magnitudes.shape) 90 | gm_strong[g_magnitudes >= threshold_high] = 1 91 | gm_weak[(g_magnitudes < threshold_high) & (g_magnitudes >= threshold_low)] = 1 92 | 93 | height, width = g_magnitudes.shape 94 | 95 | converged = False 96 | while not converged: 97 | converged = True 98 | for y in range(height): 99 | for x in range(width): 100 | if gm_weak[y, x] == 1: 101 | y_start = y-1 if y > 0 else 0 102 | y_end = y+1 if y < height else height-1 103 | x_start = x-1 if x > 0 else 0 104 | x_end = x+1 if x < width else width-1 105 | neighborhood = gm_strong[y_start:y_end+1, x_start:x_end+1] 106 | if np.sum(neighborhood) > 0: 107 | gm_weak[y, x] = 0 108 | gm_strong[y, x] = 1 109 | converged = False 110 | 111 | return gm_strong 112 | 113 | if __name__ == "__main__": 114 | main() 115 | -------------------------------------------------------------------------------- /computer-vision/crosscorrelation_convolution.py: -------------------------------------------------------------------------------- 1 | """Apply crosscorrelation and convolution to an image.""" 2 | from __future__ import division, print_function 3 | from scipy import signal 4 | import numpy as np 5 | import random 6 | from skimage import data 7 | import util 8 | np.random.seed(42) 9 | random.seed(42) 10 | 11 | def main(): 12 | """Initialize kernel, apply it to an image (via crosscorrelation, convolution).""" 13 | img = data.camera() 14 | kernel = np.array([ 15 | [-1, -2, -1], 16 | [0, 0, 0], 17 | [1, 2, 1] 18 | ]) 19 | 20 | cc_response = crosscorrelate(img, kernel) 21 | cc_gt = signal.correlate(img, kernel, mode="same") 22 | 23 | conv_response = convolve(img, kernel) 24 | conv_gt = signal.convolve(img, kernel, mode="same") 25 | 26 | util.plot_images_grayscale( 27 | [img, cc_response, cc_gt, conv_response, conv_gt], 28 | ["Image", "Cross-Correlation", "Cross-Correlation (Ground Truth)", "Convolution", "Convolution (Ground Truth)"] 29 | ) 30 | 31 | def crosscorrelate(img, kernel): 32 | """Apply a kernel/filter via crosscorrelation to an image. 33 | Args: 34 | img The image 35 | kernel The kernel/filter to apply 36 | Returns: 37 | New image 38 | """ 39 | imheight, imwidth = img.shape 40 | kheight, kwidth = kernel.shape 41 | assert len(img.shape) == 2 42 | assert kheight == kwidth # only square matrices 43 | assert kheight % 2 != 0 # sizes must be odd 44 | ksize = int((kheight - 1) / 2) 45 | im_pad = np.pad(img, ((ksize, ksize), (ksize, ksize)), mode="constant") 46 | response = np.zeros(img.shape) 47 | for y in range(ksize, ksize+imheight): 48 | for x in range(ksize, ksize+imwidth): 49 | patch = im_pad[y-ksize:y+ksize+1, x-ksize:x-ksize+1] 50 | corr = np.sum(patch * kernel) 51 | response[y-ksize, x-ksize] = corr 52 | return response 53 | 54 | def convolve(img, kernel): 55 | """Apply a kernel/filter via convolution to an image. 56 | Args: 57 | img The image 58 | kernel The kernel/filter to apply 59 | Returns: 60 | New image 61 | """ 62 | return crosscorrelate(img, np.flipud(np.fliplr(kernel))) 63 | 64 | if __name__ == "__main__": 65 | main() 66 | -------------------------------------------------------------------------------- /computer-vision/eigenfaces.py: -------------------------------------------------------------------------------- 1 | """Generate Eigenfaces from a set of training images.""" 2 | from __future__ import division, print_function 3 | from scipy import ndimage 4 | import numpy as np 5 | import random 6 | import util 7 | from sklearn.decomposition import PCA 8 | import os 9 | from scipy import misc 10 | np.random.seed(42) 11 | random.seed(42) 12 | 13 | NB_COMPONENTS = 8 14 | SCALE = 32 15 | 16 | def main(): 17 | """Load example faces, extract principal components, create Eigenfaces from 18 | PCs, plot results.""" 19 | # load images, resize them, convert to 1D-vectors 20 | images_filepaths = get_images_in_directory("images/faces/") 21 | images = [ndimage.imread(fp, flatten=True) for fp in images_filepaths] 22 | images = [misc.imresize(image, (SCALE, SCALE)) for image in images] 23 | images_vecs = np.array([image.flatten() for image in images]) 24 | 25 | # ------------ 26 | # Custom Implementation of PCA 27 | # ------------ 28 | pcs, images_vecs_transformed, images_vecs_reversed = custom_pca(images_vecs, NB_COMPONENTS) 29 | pcs = pcs.reshape((NB_COMPONENTS, SCALE, SCALE)) 30 | 31 | # plot (First example image, recovered first example image, first 8 PCs) 32 | plots_imgs = [images[0], images_vecs_reversed[0].reshape((SCALE, SCALE))] 33 | plots_titles = ["Image 0", "Image 0\n(reconstructed)"] 34 | for i in range(NB_COMPONENTS): 35 | plots_imgs.append(pcs[i]) 36 | plots_titles.append("Eigenface %d" % (i)) 37 | 38 | util.plot_images_grayscale(plots_imgs, plots_titles) 39 | 40 | 41 | # ------------ 42 | # Using the PCA implementation from scikit 43 | # ------------ 44 | # train PCA, embed image vectors, reverse the embedding (lossy) 45 | pca = PCA(NB_COMPONENTS) 46 | images_vecs_transformed = pca.fit_transform(images_vecs) 47 | images_vecs_reversed = pca.inverse_transform(images_vecs_transformed) 48 | 49 | # Extract Eigenfaces. The Eigenfaces are the principal components. 50 | pcs = pca.components_.reshape((NB_COMPONENTS, SCALE, SCALE)) 51 | 52 | # plot (First example image, recovered first example image, first 8 PCs) 53 | plots_imgs = [images[0], images_vecs_reversed[0].reshape((SCALE, SCALE))] 54 | plots_titles = ["Image 0", "Image 0\n(reconstructed)"] 55 | for i in range(NB_COMPONENTS): 56 | plots_imgs.append(pcs[i]) 57 | plots_titles.append("Eigenface %d" % (i)) 58 | 59 | util.plot_images_grayscale(plots_imgs, plots_titles) 60 | 61 | def custom_pca(images_vecs, nb_components): 62 | """Custom implementation of PCA for images. 63 | Args: 64 | images_vecs The images to transform. 65 | nb_components Number of principal components. 66 | Returns: 67 | Principal Components of shape (NB_COMPONENTS, height*width), 68 | Transformed images of shape (nb_images, NB_COMPONENTS), 69 | Reverse transformed/reconstructed images of shape (nb_images, height*width) 70 | """ 71 | imgs = np.copy(images_vecs) 72 | imgs = np.transpose(imgs) 73 | mean = np.average(imgs, axis=1) 74 | mean = mean[:, np.newaxis] 75 | A = imgs - np.tile(mean, (1, imgs.shape[1])) 76 | # Compute eigenvectors of A^TA instead of AA^T (covariance matrix) as 77 | # that is faster. 78 | L = np.dot(np.transpose(A), A) # A^TA 79 | eigenvalues, eigenvectors = np.linalg.eig(L) 80 | 81 | # recover eigenvectors of AA^T (covariance matrix) 82 | U = np.dot(A, eigenvectors) 83 | 84 | # reduce to requested number of eigenvectors 85 | U = np.transpose(U) 86 | nb_components = min(len(eigenvectors), nb_components) 87 | U = U[0:nb_components, :] 88 | 89 | # project faces to face space 90 | imgs_transformed = np.dot(U, A) 91 | imgs_transformed = np.transpose(imgs_transformed) 92 | 93 | # reconstruct faces 94 | imgs_reversed = np.dot(np.transpose(U), np.transpose(imgs_transformed)) 95 | imgs_reversed = np.transpose(imgs_reversed) 96 | 97 | return U, imgs_transformed, imgs_reversed 98 | 99 | def get_images_in_directory(directory, ext="jpg"): 100 | """Read the filepaths to all images in a directory. 101 | Args: 102 | directory Filepath to the directory. 103 | ext File extension of the images. 104 | Returns: 105 | List of filepaths 106 | """ 107 | filepaths = [] 108 | for fname in os.listdir(directory): 109 | if fname.endswith(ext) and os.path.isfile(os.path.join(directory, fname)): 110 | filepaths.append(os.path.join(directory, fname)) 111 | return filepaths 112 | 113 | if __name__ == "__main__": 114 | main() 115 | -------------------------------------------------------------------------------- /computer-vision/gauss.py: -------------------------------------------------------------------------------- 1 | """Apply gaussian filters to an image.""" 2 | from __future__ import division, print_function 3 | from scipy import signal 4 | from scipy.ndimage import filters as filters 5 | import numpy as np 6 | import random 7 | from skimage import data 8 | import util 9 | np.random.seed(42) 10 | random.seed(42) 11 | 12 | def main(): 13 | """Apply several gaussian filters one by one and plot the results each time.""" 14 | img = data.checkerboard() 15 | shapes = [(5, 5), (7, 7), (11, 11), (17, 17), (31, 31)] 16 | sigmas = [0.5, 1.0, 2.0, 4.0, 8.0] 17 | smoothed = [] 18 | for shape, sigma in zip(shapes, sigmas): 19 | smoothed = apply_gauss(img, gaussian_kernel(shape, sigma=sigma)) 20 | ground_truth = filters.gaussian_filter(img, sigma) 21 | util.plot_images_grayscale( 22 | [img, smoothed, ground_truth], 23 | ["Image", "After gauss (sigma=%.1f)" % (sigma), "Ground Truth (scipy)"] 24 | ) 25 | 26 | def apply_gauss(img, filter_mask): 27 | """Apply a gaussian filter to an image. 28 | Args: 29 | img The image 30 | filter_mask The filter mask (2D numpy array) 31 | Returns: 32 | Modified image 33 | """ 34 | return signal.correlate(img, filter_mask, mode="same") / np.sum(filter_mask) 35 | 36 | # from http://stackoverflow.com/questions/17190649/how-to-obtain-a-gaussian-filter-in-python 37 | def gaussian_kernel(shape=(3, 3), sigma=0.5): 38 | """ 39 | 2D gaussian mask - should give the same result as MATLAB's 40 | fspecial('gaussian',[shape],[sigma]) 41 | """ 42 | m, n = [(ss-1.)/2. for ss in shape] 43 | y, x = np.ogrid[-m:m+1, -n:n+1] 44 | h = np.exp(-(x*x + y*y) / (2.*sigma*sigma)) 45 | h[h < np.finfo(h.dtype).eps*h.max()] = 0 46 | sumh = h.sum() 47 | if sumh != 0: 48 | h /= sumh 49 | return h 50 | 51 | if __name__ == "__main__": 52 | main() 53 | -------------------------------------------------------------------------------- /computer-vision/gradients.py: -------------------------------------------------------------------------------- 1 | """Compute derivatives of an image with respect to x and y.""" 2 | from __future__ import division, print_function 3 | from scipy import ndimage 4 | import numpy as np 5 | import random 6 | from skimage import data 7 | import util 8 | np.random.seed(42) 9 | random.seed(42) 10 | 11 | def main(): 12 | """Load image, compute derivatives, plot.""" 13 | img = data.camera() 14 | imgy_np, imgx_np = np.gradient(img) 15 | imgx_ski, imgy_ski = _compute_derivatives(img) 16 | 17 | # dx 18 | util.plot_images_grayscale( 19 | [img, dx_symmetric(img), dx_forward(img), dx_backward(img), imgx_np, imgx_ski], 20 | ["Image", "dx (symmetric)", "dx (forward)", "dx (backward)", 21 | "Ground Truth (numpy)", "Ground Truth (scikit-image)"] 22 | ) 23 | 24 | # dy 25 | util.plot_images_grayscale( 26 | [img, dy_symmetric(img), dy_forward(img), dy_backward(img), imgy_np, imgy_ski], 27 | ["Image", "dy (symmetric)", "dy (forward)", "dy (backward)", 28 | "Ground Truth (numpy)", "Ground Truth (scikit-image)"] 29 | ) 30 | 31 | def dx_symmetric(img): 32 | """Calculate the derivative with respect to x of an image. 33 | Symmetric formula: df_dx = f(y,x+1) - f(y,x-1) 34 | Args: 35 | img The image 36 | Returns: 37 | x-derivate of the image as a new image""" 38 | imgx = np.copy(img) 39 | imgx[:, 1:-1] = imgx[:, 2:] - imgx[:, :-2] 40 | imgx[:, 0] = 0 41 | imgx[:, -1] = 0 42 | return imgx 43 | 44 | def dx_forward(img): 45 | """Calculate the derivative with respect to x of an image. 46 | Forward formula: df_dx = f(y,x+1) - f(y,x) 47 | Args: 48 | img The image 49 | Returns: 50 | x-derivate of the image as a new image""" 51 | imgx = np.copy(img) 52 | imgx[:, :-1] = imgx[:, 1:] - imgx[:, :-1] 53 | imgx[:, -1] = 0 54 | return imgx 55 | 56 | def dx_backward(img): 57 | """Calculate the derivative with respect to x of an image. 58 | Backward formula: df_dx = f(y,x) - f(y,x-1) 59 | Args: 60 | img The image 61 | Returns: 62 | x-derivate of the image as a new image""" 63 | imgx = np.copy(img) 64 | imgx[:, 1:] = imgx[:, 1:] - imgx[:, :-1] 65 | imgx[:, 0] = 0 66 | return imgx 67 | 68 | def dy_symmetric(img): 69 | """Calculate the derivative with respect to y of an image. 70 | Symmetric formula: df_dy = f(y+1,x) - f(y-1,x) 71 | Args: 72 | img The image 73 | Returns: 74 | y-derivate of the image as a new image""" 75 | return np.rot90(dx_symmetric(np.rot90(img)), 3) 76 | 77 | def dy_forward(img): 78 | """Calculate the derivative with respect to y of an image. 79 | Symmetric formula: df_dy = f(y+1,x) - f(y,x) 80 | Args: 81 | img The image 82 | Returns: 83 | y-derivate of the image as a new image""" 84 | return np.rot90(dx_forward(np.rot90(img)), 3) 85 | 86 | def dy_backward(img): 87 | """Calculate the derivative with respect to y of an image. 88 | Backward formula: df_dy = f(y,x) - f(y-1,x) 89 | Args: 90 | img The image 91 | Returns: 92 | y-derivate of the image as a new image""" 93 | return np.rot90(dx_backward(np.rot90(img)), 3) 94 | 95 | def _compute_derivatives(image, mode='constant', cval=0): 96 | """Compute derivatives the way that scikit-image does it (for comparison). 97 | This method is fully copied from the repository.""" 98 | imgy = ndimage.sobel(image, axis=0, mode=mode, cval=cval) 99 | imgx = ndimage.sobel(image, axis=1, mode=mode, cval=cval) 100 | 101 | return imgx, imgy 102 | 103 | if __name__ == "__main__": 104 | main() 105 | -------------------------------------------------------------------------------- /computer-vision/graph_image_segmentation.py: -------------------------------------------------------------------------------- 1 | """Segment an image with a simplified graph method based on internal differences 2 | (within subgraphs/segments) and external differences (between subgraphs/segments). 3 | Each pixel initially is a subgraph with edges to its 4 neighbors. Subgraphs 4 | where externalDifference < k*internalDifference are merged until convergence.""" 5 | from __future__ import division, print_function 6 | import numpy as np 7 | import random 8 | from skimage import data 9 | import util 10 | from scipy import misc 11 | from collections import defaultdict 12 | np.random.seed(42) 13 | random.seed(42) 14 | 15 | # Parameter to tune when two subgraphs/segments will be merged. 16 | # The higher the value, the more often subgraphs will be merged. 17 | K = 1.25 18 | 19 | def main(): 20 | """Load image, convert into graph, segment.""" 21 | img = data.camera() 22 | # worked decently for 32x32, not so well and slow for 64x64 23 | img = misc.imresize(img, (32, 32)) 24 | 25 | graph = image_to_graph(img) 26 | 27 | # Merge subgraphs until convergence 28 | converged = False 29 | while not converged: 30 | print("Merging (%d subgraphs left)..." % (len(graph.subgraphs))) 31 | converged = True 32 | merged = [] 33 | ediffs = graph.get_ediffs() 34 | # Iterate over all pairs subgraphs that have a connecting edge 35 | # Merge them if possible. 36 | # Do not stop after the first merge, so that we don't have to 37 | # recalculate the external differences between subgraphs many times 38 | for subgraph_a, subgraph_b, ediff in ediffs: 39 | # Try to merge the subgraphs to one subgraph 40 | if subgraph_a not in merged and subgraph_b not in merged: 41 | idiff_a = subgraph_a.get_internal_difference() 42 | idiff_b = subgraph_b.get_internal_difference() 43 | # Merge if externel difference (between two subgraphs) 44 | # is below both internal differences 45 | if ediff < K*idiff_a and ediff < K*idiff_b: 46 | graph.merge_subgraphs(subgraph_a, subgraph_b) 47 | merged.extend([subgraph_a, subgraph_b]) 48 | converged = False 49 | 50 | print("Found %d segments" % (len(graph.subgraphs))) 51 | 52 | # Create images of segments 53 | subgraphs = sorted(graph.subgraphs, key=lambda sg: len(sg.nodes), reverse=True) 54 | nb_segments = 8 55 | segment_images = [] 56 | segment_titles = [] 57 | for i in range(min(nb_segments, len(subgraphs))): 58 | segment_titles.append("Segment %d/%d" % (i, len(subgraphs))) 59 | segment_image = np.zeros(img.shape) 60 | for node in subgraphs[i].nodes: 61 | segment_image[node.y, node.x] = 255 62 | segment_images.append(segment_image) 63 | 64 | # plot images 65 | images = [img] 66 | images.extend(segment_images) 67 | titles = ["Image"] 68 | titles.extend(segment_titles) 69 | util.plot_images_grayscale(images, titles) 70 | 71 | def image_to_graph(img): 72 | """Converts an image to a graph of segments.""" 73 | height, width = img.shape 74 | 75 | # Create nodes of graph 76 | nodes = dict() 77 | idx = 0 78 | for y in range(height): 79 | for x in range(width): 80 | nodes[(y, x)] = Node(idx, y, x, img[y, x]) 81 | idx += 1 82 | 83 | # Create edges 84 | edges = defaultdict(list) 85 | for y in range(height): 86 | for x in range(width): 87 | node = nodes[(y, x)] 88 | if y > 0: 89 | edges[(y, x)].append(Edge(node, nodes[(y-1, x)])) 90 | if x < (width-1): 91 | edges[(y, x)].append(Edge(node, nodes[(y, x+1)])) 92 | if y < (height-1): 93 | edges[(y, x)].append(Edge(node, nodes[(y+1, x)])) 94 | if x > 0: 95 | edges[(y, x)].append(Edge(node, nodes[(y, x-1)])) 96 | 97 | # Create subgraphs (segments), one per pixel 98 | subgraphs = [] 99 | for y in range(height): 100 | for x in range(width): 101 | subgraphs.append(SubGraph([nodes[(y, x)]], edges[(y, x)])) 102 | 103 | # Create graph 104 | graph = Graph(subgraphs) 105 | 106 | return graph 107 | 108 | class Node(object): 109 | """Nodes (=Pixel) in the graph.""" 110 | def __init__(self, idx, y, x, value): 111 | """Create node for a pixel with a node-index, xy-coordinates and a pixel 112 | intensity value.""" 113 | self.idx = idx 114 | self.y = y 115 | self.x = x 116 | self.value = int(value) 117 | 118 | class Edge(object): 119 | """Edges between nodes.""" 120 | def __init__(self, from_node, to_node): 121 | """Create a new edge between two nodes (=pixels). Edge weight is the 122 | intensity difference between the pixels.""" 123 | self.from_node = from_node 124 | self.to_node = to_node 125 | self.weight = abs(from_node.value - to_node.value) 126 | 127 | class SubGraph(object): 128 | """Subgraphs in the graph (aka segments).""" 129 | def __init__(self, nodes, edges): 130 | """Create a new subgraph/segment with nodes and edges.""" 131 | self.nodes = nodes 132 | self.edges = edges 133 | self.update_caches() 134 | 135 | def update_caches(self): 136 | """Update the node id cache of the subgraph (ids of nodes that this 137 | subgraph contains).""" 138 | self.node_ids = set([node.idx for node in self.nodes]) 139 | 140 | def get_internal_difference(self): 141 | """Get the internal difference within the subgraph. It is simply the 142 | average of the edge weights.""" 143 | return np.average([edge.weight for edge in self.edges]) 144 | 145 | def get_external_difference(self, to_subgraph): 146 | """Get the external difference between this subgraph and another 147 | subgraph. It is the minimum edge weight between the subgraphs, or 148 | 256 if there is no connecting edge.""" 149 | weights = [] 150 | for edge in self.edges: 151 | if to_subgraph.contains_node(edge.to_node): 152 | weights.append(edge.weight) 153 | return min(weights) if len(weights) > 0 else 256 154 | 155 | def contains_node(self, node): 156 | """Returns whether a node is contained within this subgraph.""" 157 | return node.idx in self.node_ids 158 | 159 | class Graph(object): 160 | """Graph containing all subgraphs (i.e. image that contains segments).""" 161 | def __init__(self, subgraphs): 162 | """Create a new graph containing several subgraphs.""" 163 | self.subgraphs = subgraphs 164 | 165 | def merge_subgraphs(self, subgraph_a, subgraph_b): 166 | """Merge two subgraphs/segments to one subgraph/segment. Automatically 167 | extends subgraph A and removes subgraph B.""" 168 | subgraph_a.nodes.extend(subgraph_b.nodes) 169 | subgraph_a.edges.extend(subgraph_b.edges) 170 | subgraph_b.nodes = [] 171 | subgraph_b.edges = [] 172 | subgraph_a.update_caches() 173 | subgraph_b.update_caches() 174 | self.subgraphs = [subgraph for subgraph in self.subgraphs if subgraph != subgraph_b] 175 | 176 | def get_ediffs(self): 177 | """Calculate the external differences of all pairs of subgraphs within 178 | the graph. This is an expensive operation, especially before the first 179 | merges.""" 180 | ediffs = [] 181 | for i in range(len(self.subgraphs)): 182 | for j in range(i+1, len(self.subgraphs)): 183 | ediff = self.subgraphs[i].get_external_difference(self.subgraphs[j]) 184 | ediffs.append((self.subgraphs[i], self.subgraphs[j], ediff)) 185 | ediffs = sorted(ediffs, key=lambda t: t[2]) 186 | return ediffs 187 | 188 | if __name__ == "__main__": 189 | main() 190 | -------------------------------------------------------------------------------- /computer-vision/harris.py: -------------------------------------------------------------------------------- 1 | """Calculate the harris corner score of an image.""" 2 | from __future__ import division, print_function 3 | from scipy import signal, ndimage 4 | import numpy as np 5 | import random 6 | from skimage import data 7 | from skimage import feature 8 | from skimage import util as skiutil 9 | import util 10 | np.random.seed(42) 11 | random.seed(42) 12 | 13 | def main(): 14 | """Load image, calculate harris scores (window functions: matrix of ones, gauss) 15 | and plot the results.""" 16 | img = data.checkerboard() 17 | score_window = harris_ones(img, 7) 18 | score_gauss = harris_gauss(img) 19 | util.plot_images_grayscale( 20 | [img, score_window, score_gauss, feature.corner_harris(img)], 21 | ["Image", "Harris-Score (ones)", "Harris-Score (gauss)", "Harris-Score (ground truth)"] 22 | ) 23 | 24 | def harris_gauss(img, sigma=1, k=0.05): 25 | """Calculate the harris score based on a gauss window function. 26 | Args: 27 | img The image to use for corner detection. 28 | sigma The sigma value for the gauss functions. 29 | k Weighting parameter during the final scoring (det vs. trace). 30 | Returns: 31 | Corner score image""" 32 | # Gradients 33 | img = skiutil.img_as_float(img) 34 | imgy, imgx = np.gradient(img) 35 | 36 | imgxy = imgx * imgy 37 | imgxx = imgx ** 2 38 | imgyy = imgy ** 2 39 | 40 | # compute parts of harris matrix 41 | a11 = ndimage.gaussian_filter(imgxx, sigma=sigma, mode="constant") 42 | a12 = ndimage.gaussian_filter(imgxy, sigma=sigma, mode="constant") 43 | a21 = a12 44 | a22 = ndimage.gaussian_filter(imgyy, sigma=sigma, mode="constant") 45 | 46 | # compute score per pixel 47 | det_a = a11 * a22 - a12 * a21 48 | trace_a = a11 + a22 49 | score = det_a - k * trace_a ** 2 50 | 51 | return score 52 | 53 | def harris_ones(img, window_size, k=0.05): 54 | """Calculate the harris score based on a window function of diagonal ones. 55 | Args: 56 | img The image to use for corner detection. 57 | window_size Size of the window (NxN). 58 | k Weighting parameter during the final scoring (det vs. trace). 59 | Returns: 60 | Corner score image 61 | """ 62 | # Gradients 63 | img = skiutil.img_as_float(img) 64 | imgy, imgx = np.gradient(img) 65 | 66 | imgxy = imgx * imgy 67 | imgxx = imgx ** 2 68 | imgyy = imgy ** 2 69 | 70 | # window function (matrix of diagonal ones) 71 | window = np.ones((window_size, window_size)) 72 | 73 | # compute parts of harris matrix 74 | a11 = signal.correlate(imgxx, window, mode="same") / window_size 75 | a12 = signal.correlate(imgxy, window, mode="same") / window_size 76 | a21 = a12 77 | a22 = signal.correlate(imgyy, window, mode="same") / window_size 78 | 79 | # compute score per pixel 80 | det_a = a11 * a22 - a12 * a21 81 | trace_a = a11 + a22 82 | 83 | return det_a - k * trace_a ** 2 84 | 85 | if __name__ == "__main__": 86 | main() 87 | -------------------------------------------------------------------------------- /computer-vision/histogram_equalization.py: -------------------------------------------------------------------------------- 1 | """Apply histogram equalization techniques to an image.""" 2 | from __future__ import division, print_function 3 | import numpy as np 4 | import random 5 | from skimage import data 6 | import matplotlib.pyplot as plt 7 | np.random.seed(42) 8 | random.seed(42) 9 | 10 | def main(): 11 | """Load image, apply histogram stretching and cumulative histogram 12 | equalization. Plot the results.""" 13 | img = data.text() 14 | 15 | height, width = img.shape 16 | nb_pixels = height * width 17 | 18 | # apply histogram stretching 19 | gray_min = np.min(img) 20 | gray_max = np.max(img) 21 | img_hist_stretched = (img - gray_min) * (255 / (gray_max - gray_min)) 22 | img_hist_stretched = img_hist_stretched.astype(np.uint8) 23 | 24 | # apply cumulative histogram equalization 25 | img_cumulative = np.zeros(img.shape, dtype=np.uint8) 26 | hist_cumulative = [0] * 256 27 | running_sum = 0 28 | hist, edges = np.histogram(img, 256, (0, 255)) 29 | for i in range(256): 30 | count = hist[i] 31 | running_sum += count 32 | hist_cumulative[i] = running_sum / nb_pixels 33 | 34 | for i in range(256): 35 | img_cumulative[img == i] = int(256 * hist_cumulative[i]) 36 | 37 | # plot 38 | plot_images_grayscale( 39 | [img, img_hist_stretched, img_cumulative], 40 | ["Image", "Histogram stretched to 0-255", "Cumulative Histogram Equalization"] 41 | ) 42 | 43 | def plot_images_grayscale(images, titles, no_axis=False): 44 | """Plot images with their histograms. 45 | Args: 46 | images List of images 47 | titles List of titles of images 48 | no_axis Whether to show the x/y axis""" 49 | fig = plt.figure() 50 | for i, (image, title) in enumerate(zip(images, titles)): 51 | ax = fig.add_subplot(2, len(images), i+1) 52 | ax.set_title(title) 53 | if no_axis: 54 | ax.get_xaxis().set_visible(False) 55 | ax.get_yaxis().set_visible(False) 56 | plt.imshow(image, cmap=plt.cm.gray) 57 | 58 | for i, image in enumerate(images): 59 | hist, edges = np.histogram(image, 256, (0, 255)) 60 | 61 | ax = fig.add_subplot(2, len(images), len(images)+i+1) 62 | if no_axis: 63 | ax.get_xaxis().set_visible(False) 64 | ax.get_yaxis().set_visible(False) 65 | plt.bar(range(256), hist) 66 | 67 | plt.show() 68 | 69 | if __name__ == "__main__": 70 | main() 71 | -------------------------------------------------------------------------------- /computer-vision/hough.py: -------------------------------------------------------------------------------- 1 | """Apply Hough Transform to an image.""" 2 | from __future__ import division, print_function 3 | import numpy as np 4 | import random 5 | from skimage import draw 6 | from scipy import signal 7 | import util 8 | import math 9 | np.random.seed(42) 10 | random.seed(42) 11 | 12 | NB_QUANTIZATION_STEPS = 128 13 | 14 | def main(): 15 | """Create a noisy line image, recover the line via hough transform and 16 | plot an unnoise image with that line.""" 17 | # generate example image (noisy lines) 18 | img = np.zeros((128, 128)) 19 | for y in range(12, 120): 20 | img[y, y] = 1 21 | for y in range(40, 75): 22 | img[y, 12] = 1 23 | for x in range(16, 64): 24 | img[int(10 + x*0.2), x] = 1 25 | img = (img * 100) + np.random.binomial(80, 0.5, (img.shape)) 26 | 27 | accumulator, local_maxima, img_hough = hough(img, 5) 28 | 29 | util.plot_images_grayscale( 30 | [img, accumulator, local_maxima, img_hough], 31 | ["Image", "Accumulator content", "Local Maxima", "Line from Hough Transform"] 32 | ) 33 | 34 | def grad_magnitude(img): 35 | """Calculate the gradient magnitude of an image. 36 | Args: 37 | img The image 38 | Returns: 39 | gradient image""" 40 | img = img / 255.0 41 | sobel_y = np.array([ 42 | [-1, -2, -1], 43 | [0, 0, 0], 44 | [1, 2, 1] 45 | ]) 46 | sobel_x = np.rot90(sobel_y) # rotates counter-clockwise 47 | 48 | # apply x/y sobel filter to get x/y gradients 49 | imgx = signal.correlate(img, sobel_x, mode="same") 50 | imgy = signal.correlate(img, sobel_y, mode="same") 51 | imgmag = np.sqrt(imgx**2 + imgy**2) 52 | 53 | return imgmag 54 | 55 | def hough(img, nb_lines): 56 | """Applies the Hough Transformation to an image. 57 | Args: 58 | img The image 59 | nb_lines The number of lines to search for. 60 | Returns: 61 | Accumulator image, 62 | Local maxima in accumulator image, 63 | image with detected lines""" 64 | height, width = img.shape 65 | magnitude = grad_magnitude(img) 66 | mag_avg = np.average(magnitude) 67 | 68 | max_d = math.sqrt(height**2 + width**2) 69 | min_d = -max_d 70 | alphas = np.linspace(0, np.pi, NB_QUANTIZATION_STEPS) 71 | distances = np.linspace(min_d, max_d, NB_QUANTIZATION_STEPS) 72 | accumulator = np.zeros((NB_QUANTIZATION_STEPS, NB_QUANTIZATION_STEPS)) 73 | for y in range(1, height-1): 74 | for x in range(1, width-1): 75 | if magnitude[y, x] > mag_avg: 76 | for alpha_idx, alpha in enumerate(alphas): 77 | distance = x * math.cos(alpha) + y * math.sin(alpha) 78 | distance_idx = util.quantize_idx(distance, distances) 79 | accumulator[alpha_idx, distance_idx] += 1 80 | 81 | img_hough = np.zeros((height, width, 3)) 82 | img_hough[:, :, 0] = np.copy(img) 83 | img_hough[:, :, 1] = np.copy(img) 84 | img_hough[:, :, 2] = np.copy(img) 85 | 86 | local_maxima = find_local_maxima(accumulator) 87 | peaks_idx = get_peak_indices(local_maxima, nb_lines) 88 | for value, (alpha_idx, distance_idx) in peaks_idx: 89 | peak_alpha_rad = alphas[alpha_idx] 90 | peak_distance = distances[distance_idx] 91 | 92 | x0 = 0 93 | x1 = width - 1 94 | y0 = (peak_distance - 0 * np.cos(peak_alpha_rad)) / (np.sin(peak_alpha_rad) + 1e-8) 95 | y1 = (peak_distance - (width-1) * np.cos(peak_alpha_rad)) / (np.sin(peak_alpha_rad) + 1e-8) 96 | 97 | y0 = np.clip(y0, 0, height-1) 98 | y1 = np.clip(y1, 0, height-1) 99 | 100 | rr, cc = draw.line(int(y0), int(x0), int(y1), int(x1)) 101 | img_hough[rr, cc, 0] = 1 102 | img_hough[rr, cc, 1] = 0 103 | img_hough[rr, cc, 2] = 0 104 | 105 | return accumulator, local_maxima, img_hough 106 | 107 | def find_local_maxima(arr, size=5): 108 | """Finds the local maxima in an image. 109 | Args: 110 | arr The image 111 | size Neighborhood size (3 => 3x3) 112 | Returns: 113 | Local maxima image""" 114 | ssize = int((size-1)/2) 115 | arr = np.copy(arr) 116 | peaks = np.zeros(arr.shape) 117 | h, w = arr.shape 118 | for y in range(ssize, h-ssize): 119 | for x in range(ssize, w-ssize): 120 | val = arr[y, x] 121 | if val > 0: 122 | neighborhood = np.copy(arr[y-ssize:y+ssize+1, x-ssize:x+ssize+1]) 123 | neighborhood[ssize, ssize] = 0 124 | if val > np.max(neighborhood): 125 | peaks[y, x] = val 126 | return peaks 127 | 128 | def get_peak_indices(arr, n): 129 | """Finds the indices of the n highest values in an array. 130 | Args: 131 | arr Array to analyze. 132 | n Number of values. 133 | Returns: 134 | List of (value, (dim1 idx, dim2 idx, ...))""" 135 | indices = arr.ravel().argsort()[-n:] 136 | indices = (np.unravel_index(i, arr.shape) for i in indices) 137 | return [(arr[i], i) for i in indices] 138 | 139 | if __name__ == "__main__": 140 | main() 141 | -------------------------------------------------------------------------------- /computer-vision/images/faces/0000.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0000.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0001.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0001.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0002.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0002.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0003.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0003.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0004.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0004.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0005.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0005.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0006.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0006.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0007.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0007.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0008.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0008.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0009.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0009.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0010.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0010.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0011.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0011.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0012.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0012.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0013.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0013.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0014.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0014.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0015.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0015.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0016.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0016.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0017.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0017.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0018.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0018.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0019.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0019.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0020.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0020.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0021.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0021.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0022.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0022.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0023.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0023.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0024.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0024.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0025.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0025.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0026.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0026.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0027.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0027.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0028.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0028.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0029.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0029.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0030.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0030.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0031.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0031.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0032.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0032.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0033.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0033.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0034.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0034.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0035.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0035.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0036.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0036.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0037.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0037.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0038.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0038.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0039.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0039.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0040.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0040.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0041.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0041.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0042.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0042.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0043.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0043.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0044.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0044.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0045.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0045.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0046.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0046.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0047.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0047.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0048.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0048.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0049.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0049.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0050.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0050.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0051.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0051.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0052.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0052.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0053.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0053.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0054.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0054.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0055.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0055.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0056.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0056.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0057.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0057.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0058.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0058.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0059.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0059.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0060.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0060.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0061.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0061.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0062.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0062.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0063.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0063.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0064.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0064.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0065.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0065.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0066.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0066.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0067.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0067.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0068.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0068.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0069.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0069.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0070.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0070.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0071.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0071.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0072.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0072.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0073.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0073.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0074.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0074.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0075.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0075.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0076.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0076.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0077.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0077.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0078.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0078.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0079.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0079.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0080.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0080.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0081.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0081.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0082.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0082.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0083.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0083.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0084.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0084.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0085.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0085.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0086.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0086.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0087.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0087.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0088.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0088.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0089.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0089.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0090.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0090.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0091.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0091.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0092.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0092.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0093.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0093.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0094.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0094.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0095.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0095.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0096.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0096.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0097.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0097.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0098.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0098.jpg -------------------------------------------------------------------------------- /computer-vision/images/faces/0099.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aleju/computer-vision-algorithms/f09c212be5b550ae565114a33bb609380018211a/computer-vision/images/faces/0099.jpg -------------------------------------------------------------------------------- /computer-vision/mean_shift_segmentation.py: -------------------------------------------------------------------------------- 1 | """Segment an image using mean shift clustering.""" 2 | from __future__ import division, print_function 3 | import numpy as np 4 | import random 5 | from skimage import data 6 | from skimage import color 7 | from sklearn.cluster import MeanShift, estimate_bandwidth 8 | from scipy import misc 9 | import matplotlib.pyplot as plt 10 | np.random.seed(42) 11 | random.seed(42) 12 | 13 | def main(): 14 | """Load image, collect pixels, cluster, create segment images, plot.""" 15 | # load image 16 | img_rgb = data.coffee() 17 | img_rgb = misc.imresize(img_rgb, (256, 256)) / 255.0 18 | img = color.rgb2hsv(img_rgb) 19 | height, width, channels = img.shape 20 | print("Image shape is: ", img.shape) 21 | 22 | # collect pixels as tuples of (r, g, b, y, x) 23 | print("Collecting pixels...") 24 | pixels = [] 25 | for y in range(height): 26 | for x in range(width): 27 | pixel = img[y, x, ...] 28 | pixels.append([pixel[0], pixel[1], pixel[2], (y/height)*2.0, (x/width)*2.0]) 29 | pixels = np.array(pixels) 30 | print("Found %d pixels to cluster" % (len(pixels))) 31 | 32 | # cluster the pixels using mean shift 33 | print("Clustering...") 34 | bandwidth = estimate_bandwidth(pixels, quantile=0.05, n_samples=500) 35 | clusterer = MeanShift(bandwidth=bandwidth, bin_seeding=True) 36 | labels = clusterer.fit_predict(pixels) 37 | 38 | # process labels generated during clustering 39 | labels_unique = set(labels) 40 | labels_counts = [(lu, len([l for l in labels if l == lu])) for lu in labels_unique] 41 | labels_unique = sorted(list(labels_unique), key=lambda l: labels_counts[l], reverse=True) 42 | nb_clusters = len(labels_unique) 43 | print("Found %d clusters" % (nb_clusters)) 44 | print(labels.shape) 45 | 46 | print("Creating images of segments...") 47 | img_segments = [np.copy(img_rgb)*0.25 for label in labels_unique] 48 | 49 | for y in range(height): 50 | for x in range(width): 51 | pixel_idx = (y*width) + x 52 | label = labels[pixel_idx] 53 | img_segments[label][y, x, 0] = 1.0 54 | 55 | print("Plotting...") 56 | images = [img_rgb] 57 | titles = ["Image"] 58 | for i in range(min(8, nb_clusters)): 59 | images.append(img_segments[i]) 60 | titles.append("Segment %d" % (i)) 61 | 62 | plot_images(images, titles) 63 | 64 | def plot_images(images, titles, no_axis=False): 65 | """Plot images with titles. 66 | Args: 67 | images List of images to plot (color or grayscale). 68 | titles List of titles of the images. 69 | no_axis Whether to show x/y axis.""" 70 | fig = plt.figure() 71 | for i, (image, title) in enumerate(zip(images, titles)): 72 | ax = fig.add_subplot(1, len(images), i+1) 73 | ax.set_title(title) 74 | if no_axis: 75 | ax.get_xaxis().set_visible(False) 76 | ax.get_yaxis().set_visible(False) 77 | if len(image.shape) == 2: 78 | plt.imshow(image, cmap=plt.cm.gray) 79 | else: 80 | plt.imshow(image) 81 | plt.show() 82 | 83 | if __name__ == "__main__": 84 | main() 85 | -------------------------------------------------------------------------------- /computer-vision/otsu.py: -------------------------------------------------------------------------------- 1 | """Binarize an image using the Otsu method.""" 2 | from __future__ import division, print_function 3 | import numpy as np 4 | import random 5 | from skimage import data 6 | from skimage import filters as skifilters 7 | import util 8 | np.random.seed(42) 9 | random.seed(42) 10 | 11 | def main(): 12 | """Load image, calculate optimal threshold, binarize, plot.""" 13 | # load image 14 | img = data.coins() 15 | height, width = img.shape 16 | nb_pixels = height * width 17 | 18 | # precalculate some values for speedup 19 | # average pixel value 20 | g_avg = np.average(img) 21 | # P(pixel-value), i.e. #pixels-with-value / #all-pixels 22 | p_g = [0] * 256 23 | for g in range(0, 256): 24 | p_g[g] = np.sum(img == g) / nb_pixels 25 | 26 | # Otsu method 27 | # calculations are based on standard formulas 28 | q_best = None 29 | threshold_best = None 30 | img_bin_best = None 31 | # iterate over all possible thresholds 32 | for t in range(1, 255): 33 | img_bin = np.zeros(img.shape) 34 | img_bin[img >= t] = 1 35 | 36 | p1 = np.sum(img_bin) / nb_pixels 37 | p0 = 1 - p1 38 | 39 | g0 = np.average(img[img_bin == 0]) if np.sum(img[img_bin == 0]) > 0 else 0 40 | g1 = np.average(img[img_bin == 1]) if np.sum(img[img_bin == 1]) > 0 else 0 41 | 42 | var0 = sum([(g-g0)**2 * p_g[g] for g in range(0, t+1)]) 43 | var1 = sum([(g-g1)**2 * p_g[g] for g in range(t+1, 256)]) 44 | 45 | var_between = p0 * (g0 - g_avg)**2 + p1 * (g1 - g_avg)**2 46 | var_inner = p0 * var0**2 + p1 * var1**2 47 | 48 | # q is the relation of variance between classes and variance within classes 49 | q = var_between / var_inner if var_inner > 0 else 0 50 | 51 | print(t, p0, p1, g0, g1, g_avg, var_between, var_inner, q) 52 | if q_best is None or q_best < q: 53 | q_best = q 54 | threshold_best = t 55 | img_bin_best = img <= t 56 | 57 | # ground truth, based on scikit-image 58 | gt_tresh = skifilters.threshold_otsu(img) 59 | ground_truth = img <= gt_tresh 60 | 61 | # plot 62 | util.plot_images_grayscale( 63 | [img, img_bin_best, ground_truth], 64 | ["Image", "Otsu", "Otsu (Ground Truth)"] 65 | ) 66 | 67 | if __name__ == "__main__": 68 | main() 69 | -------------------------------------------------------------------------------- /computer-vision/prewitt.py: -------------------------------------------------------------------------------- 1 | """Apply Prewitt filter to an image to extract the x/y gradients.""" 2 | from __future__ import division, print_function 3 | from scipy import signal 4 | import numpy as np 5 | import random 6 | from skimage import data 7 | from skimage import filters as skifilters 8 | import util 9 | np.random.seed(42) 10 | random.seed(42) 11 | 12 | def main(): 13 | """Load image, apply filter, plot.""" 14 | img = data.camera() 15 | 16 | # just like sobel, but no -2/+2 in the middle 17 | prewitt_y = np.array([ 18 | [-1, -1, -1], 19 | [0, 0, 0], 20 | [1, 1, 1] 21 | ]) 22 | prewitt_x = np.rot90(prewitt_y) # rotates counter-clockwise 23 | 24 | img_sx = signal.correlate(img, prewitt_x, mode="same") 25 | img_sy = signal.correlate(img, prewitt_y, mode="same") 26 | g_magnitude = np.sqrt(img_sx**2 + img_sy**2) 27 | 28 | ground_truth = skifilters.prewitt(data.camera()) 29 | 30 | util.plot_images_grayscale( 31 | [img, img_sx, img_sy, g_magnitude, ground_truth], 32 | ["Image", "Prewitt (x)", "Prewitt (y)", "Prewitt (both/magnitude)", 33 | "Prewitt (Ground Truth)"] 34 | ) 35 | 36 | if __name__ == "__main__": 37 | main() 38 | -------------------------------------------------------------------------------- /computer-vision/rank_order.py: -------------------------------------------------------------------------------- 1 | """Apply rank order filters to an image.""" 2 | from __future__ import division, print_function 3 | import numpy as np 4 | import random 5 | from skimage import data 6 | import util 7 | np.random.seed(42) 8 | random.seed(42) 9 | 10 | def main(): 11 | """Load image, apply filters and plot the results.""" 12 | img = data.moon() 13 | util.plot_images_grayscale( 14 | [img, median(img), morphological_edge(img), 15 | erosion(img), dilation(img), 16 | opening(img), closing(img)], 17 | ["Image", "Median", "Morphological Edge", "Erosion", "Dilation", "Opening", "Closing"] 18 | ) 19 | 20 | def median(img): 21 | """Apply a median rank order filter to an image. 22 | Picks for each pixel the median pixel value in the neighborhood around 23 | the pixel. 24 | Args: 25 | img The input image 26 | Returns: 27 | Modified image""" 28 | return rank_order(img, np.median) 29 | 30 | def erosion(img): 31 | """Apply an erosion rank order filter to an image. 32 | Picks for each pixel the lowest value in the neighborhood around 33 | the pixel. 34 | Args: 35 | img The input image 36 | Returns: 37 | Modified image""" 38 | return rank_order(img, lambda neighborhood: neighborhood[0]) 39 | 40 | def dilation(img): 41 | """Apply a dilation rank order filter to an image. 42 | Picks for each pixel the highest pixel value in the neighborhood around 43 | the pixel. 44 | Args: 45 | img The input image 46 | Returns: 47 | Modified image""" 48 | return rank_order(img, lambda neighborhood: neighborhood[-1]) 49 | 50 | def morphological_edge(img): 51 | """Apply a edge detection rank order filter to an image. 52 | Changes each pixel to the difference of the highest and lowest value in the 53 | neighborhood around the pixel. 54 | Args: 55 | img The input image 56 | Returns: 57 | Modified image""" 58 | return rank_order(img, lambda neighborhood: neighborhood[-1] - neighborhood[0]) 59 | 60 | def opening(img): 61 | """Apply an opening rank order filter to an image. 62 | Applies first an erosion and then a dilation. Useful to get rid of smaller 63 | shapes/artefacts. 64 | Args: 65 | img The input image 66 | Returns: 67 | Modified image""" 68 | return dilation(erosion(img)) 69 | 70 | def closing(img): 71 | """Apply a closing rank order filter to an image. 72 | Applies first a dilation and then an erosion. Useful to fill small 73 | holes/gaps. 74 | Args: 75 | img The input image 76 | Returns: 77 | Modified image""" 78 | return erosion(dilation(img)) 79 | 80 | def rank_order(img, callback): 81 | """General rank order filter. 82 | Applies a callback to each pixel in the image. The callback receives the 83 | sorted pixel values in the neighborhood around the pixel and has to return 84 | a new pixel value. 85 | Args: 86 | img The input image 87 | callback The callback function to apply to each pixel neihborhood 88 | Returns: 89 | Modified image""" 90 | result = np.zeros(img.shape) 91 | height, width = img.shape 92 | for y in range(height): 93 | for x in range(width): 94 | y_start = y-1 if y > 0 else 0 95 | y_end = y+1 if y < height else height-1 96 | x_start = x-1 if x > 0 else 0 97 | x_end = x+1 if x < width else width-1 98 | neighborhood = img[y_start:y_end+1, x_start:x_end+1].flatten() 99 | neighborhood = np.array(sorted(list(neighborhood))) 100 | result[y, x] = callback(neighborhood) 101 | return result 102 | 103 | if __name__ == "__main__": 104 | main() 105 | -------------------------------------------------------------------------------- /computer-vision/sift.py: -------------------------------------------------------------------------------- 1 | """Apply a simplified version of the SIFT keypoint locator to an image.""" 2 | from __future__ import division, print_function 3 | from scipy.ndimage import filters as filters 4 | import numpy as np 5 | import random 6 | from skimage import data 7 | from skimage import util as skiutil 8 | from skimage import draw 9 | import util 10 | import math 11 | import matplotlib.pyplot as plt 12 | np.random.seed(42) 13 | random.seed(42) 14 | 15 | def main(): 16 | """Load image, calculate Difference of Gaussians (DoG), find keypoints, 17 | draw them on the image, plot the results.""" 18 | # load image 19 | img = skiutil.img_as_float(data.camera()) 20 | 21 | # calculate Difference of Gaussians 22 | dog = generate_dog(img, 3, 4) 23 | 24 | # find keypoints 25 | keypoints = find_keypoints(img, dog) 26 | print("Found %d keypoints" % len(keypoints)) 27 | 28 | # draw keypoints 29 | img = draw_keypoints(img, keypoints, 0.005) 30 | 31 | # plot 32 | plot_images([img], ["Image with 0.5% of all keypoints"]) 33 | 34 | def generate_dog(img, nb_octaves, nb_per_octave=4): 35 | """Generate the difference of gaussians of an image. 36 | Args: 37 | img The input image 38 | nb_octaves Number of octaves (groups of images with similar smoothing/sigmas) 39 | nb_per_octave Number of images in one octave (with increasing smoothing/sigmas) 40 | Returns: 41 | List of (difference image, sigma value) 42 | """ 43 | spaces = [] 44 | sigma_start = 1.6 45 | k_start = math.sqrt(2) 46 | for i in range(nb_octaves): 47 | sigma = sigma_start * (2 ** i) 48 | last_gauss = None 49 | for j in range(nb_per_octave+1): 50 | k = k_start ** (j+1) 51 | gauss = filters.gaussian_filter(img, k*sigma) 52 | if last_gauss is not None: 53 | diff = gauss - last_gauss 54 | spaces.append((diff, k*sigma)) 55 | last_gauss = gauss 56 | return spaces 57 | 58 | def find_keypoints(img, dog): 59 | """Find keypoints in an image using DoG-images. 60 | Args: 61 | img The image in which to find keypoints. 62 | dog The Difference of Gaussians images. 63 | Returns: 64 | List of ((y, x), orientation, id of DoG-image, sigma value of scale, "min"/"max") 65 | """ 66 | extrema = [] 67 | for scale_idx, (scale, scale_size) in enumerate(dog): 68 | print("Finding keypoints in scale with id %d, sigma %.2f" % (scale_idx, scale_size)) 69 | 70 | # upwords neighbor 71 | nup = dog[scale_idx+1][0] if scale_idx+1 < len(dog) else None 72 | # downwards neighbor 73 | ndown = dog[scale_idx-1][0] if scale_idx > 0 else None 74 | 75 | grad_y, grad_x = np.gradient(scale) 76 | grad_orientation_rad = np.arctan2(grad_y, grad_x) 77 | 78 | height, width = scale.shape 79 | for y in range(1, height-1): 80 | for x in range(1, width-1): 81 | # extract 3x3 patch around the current pixel 82 | patch_img = img[y-1:y+2, x-1:x+2] 83 | 84 | # add only keypoints to the end results which are if areas 85 | # of higher contrast (to reduce the number of bad keypoints) 86 | if not is_low_contrast(patch_img): 87 | center_val = scale[y+1, x+1] # value of the center pixel in the patch 88 | orientation = np.degrees(grad_orientation_rad[y+1, x+1]) 89 | 90 | # collect neighboring pixels in neighboring scales 91 | neighbors = [scale[y-1, x], scale[y, x+1], scale[y+1, x], scale[y, x-1]] 92 | neighbors.extend( 93 | [scale[y-1, x-1], scale[y-1, x+1], 94 | scale[y+1, x+1], scale[y+1, x-1]] 95 | ) 96 | if nup is not None: 97 | neighbors.extend( 98 | [nup[y-1, x], nup[y, x+1], 99 | nup[y+1, x], nup[y, x-1]] 100 | ) 101 | if ndown is not None: 102 | neighbors.extend( 103 | [ndown[y-1, x], ndown[y, x+1], 104 | ndown[y+1, x], ndown[y, x-1]] 105 | ) 106 | 107 | # only add keypoints which are the maximum or minimum among 108 | # their collected neighbors (i.e. get local maxima/minima) 109 | # (y+1, x+1) to get the center of the extracted patch 110 | if center_val >= max(neighbors): 111 | extrema.append(((y+1, x+1), orientation, scale_idx, scale_size, "max")) 112 | elif center_val <= min(neighbors): 113 | extrema.append(((y+1, x+1), orientation, scale_idx, scale_size, "min")) 114 | return extrema 115 | 116 | def is_low_contrast(patch): 117 | """Estimate whether a patch is probably from a low contrast area in the image. 118 | Uses max() - min() instead of average/median, as that is faster. 119 | Args: 120 | patch The patch in the image 121 | Returns: 122 | boolean""" 123 | return np.max(patch) - np.min(patch) < 0.1 124 | 125 | def draw_keypoints(img, keypoints, draw_prob): 126 | """Draws for each keypoint a circle (roughly matching the sigma of the scale) 127 | with a line for the orientation. 128 | Args: 129 | img The image to which to add the keypoints (gets copied) 130 | keypoints The keypoints to draw 131 | draw_prob Probability of drawing a keypoint (the lower the less keypoints are drawn) 132 | Returns: 133 | Image with keypoints""" 134 | height, width = img.shape 135 | img = np.copy(img) 136 | 137 | # convert from grayscale image to RGB so that keypoints can be drawn in red 138 | img = img[:, :, np.newaxis] 139 | img = np.repeat(img, 3, axis=2) 140 | 141 | for (y, x), orientation, scale_idx, scale_size, kp_type in keypoints: 142 | if draw_prob < 1.0 and random.random() <= draw_prob: 143 | # draw the circle 144 | radius = int(scale_size) 145 | rr, cc = draw.circle_perimeter(y, x, radius, shape=img.shape) 146 | img[rr, cc, 0] = 1.0 147 | img[rr, cc, 1:] = 0 148 | 149 | # draw orientation 150 | orientation = util.quantize(orientation, [-135, -90, -45, 0, 45, 90, 135, 180]) 151 | x_start = x 152 | y_start = y 153 | if orientation == 0: 154 | x_end = x + radius 155 | y_end = y 156 | elif orientation == 45: 157 | x_end = x + radius*(1/math.sqrt(2)) 158 | y_end = y + radius*(1/math.sqrt(2)) 159 | elif orientation == 90: 160 | x_end = x 161 | y_end = y + radius 162 | elif orientation == 135: 163 | x_end = x - radius*(1/math.sqrt(2)) 164 | y_end = y - radius*(1/math.sqrt(2)) 165 | elif orientation == 180: 166 | x_end = x - radius 167 | y_end = y 168 | elif orientation == -135: 169 | x_end = x - radius*(1/math.sqrt(2)) 170 | y_end = y - radius*(1/math.sqrt(2)) 171 | elif orientation == -90: 172 | x_end = x 173 | y_end = y - radius 174 | elif orientation == -45: 175 | x_end = x + radius*(1/math.sqrt(2)) 176 | y_end = y - radius*(1/math.sqrt(2)) 177 | x_end = np.clip(x_end, 0, width-1) 178 | y_end = np.clip(y_end, 0, height-1) 179 | rr, cc = draw.line(int(y_start), int(x_start), int(y_end), int(x_end)) 180 | img[rr, cc, 0] = 1.0 181 | img[rr, cc, 1:] = 0 182 | img = np.clip(img, 0, 1.0) 183 | 184 | return img 185 | 186 | def plot_images(images, titles, no_axis=False): 187 | """Plot RGB images. 188 | Args: 189 | images List of RGB images 190 | titles List of titles for each image 191 | no_axis Whether to show x/y axis""" 192 | fig = plt.figure() 193 | for i, (image, title) in enumerate(zip(images, titles)): 194 | ax = fig.add_subplot(1, len(images), i+1) 195 | ax.set_title(title) 196 | if no_axis: 197 | ax.get_xaxis().set_visible(False) 198 | ax.get_yaxis().set_visible(False) 199 | plt.imshow(image) 200 | plt.show() 201 | 202 | if __name__ == "__main__": 203 | main() 204 | -------------------------------------------------------------------------------- /computer-vision/sobel.py: -------------------------------------------------------------------------------- 1 | """Apply Sobel filter to an image.""" 2 | from __future__ import division, print_function 3 | from scipy import signal 4 | import numpy as np 5 | import random 6 | from skimage import data 7 | from skimage import filters as skifilters 8 | import util 9 | np.random.seed(42) 10 | random.seed(42) 11 | 12 | def main(): 13 | """Load image, apply sobel (to get x/y gradients), plot the results.""" 14 | img = data.camera() 15 | 16 | sobel_y = np.array([ 17 | [-1, -2, -1], 18 | [0, 0, 0], 19 | [1, 2, 1] 20 | ]) 21 | sobel_x = np.rot90(sobel_y) # rotates counter-clockwise 22 | 23 | # apply x/y sobel filter to get x/y gradients 24 | img_sx = signal.correlate(img, sobel_x, mode="same") 25 | img_sy = signal.correlate(img, sobel_y, mode="same") 26 | 27 | # combine x/y gradients to gradient magnitude 28 | # scikit-image's implementation divides by sqrt(2), not sure why 29 | img_s = np.sqrt(img_sx**2 + img_sy**2) / np.sqrt(2) 30 | 31 | # create binarized image 32 | threshold = np.average(img_s) 33 | img_s_bin = np.zeros(img_s.shape) 34 | img_s_bin[img_s > threshold] = 1 35 | 36 | # generate ground truth (scikit-image method) 37 | ground_truth = skifilters.sobel(data.camera()) 38 | 39 | # plot 40 | util.plot_images_grayscale( 41 | [img, img_sx, img_sy, img_s, img_s_bin, ground_truth], 42 | ["Image", "Sobel (x)", "Sobel (y)", "Sobel (magnitude)", 43 | "Sobel (magnitude, binarized)", "Sobel (Ground Truth)"] 44 | ) 45 | 46 | if __name__ == "__main__": 47 | main() 48 | -------------------------------------------------------------------------------- /computer-vision/template_matching.py: -------------------------------------------------------------------------------- 1 | """Finds an example image in a larger image via template matching.""" 2 | from __future__ import division, print_function 3 | import numpy as np 4 | import random 5 | from skimage import data 6 | from skimage import util as skiutil 7 | import util 8 | np.random.seed(42) 9 | random.seed(42) 10 | 11 | def main(): 12 | """Load template image (needle) and the large example image (haystack), 13 | generate matching score per pixel, plot the results.""" 14 | img_haystack = skiutil.img_as_float(data.camera()) # the image in which to search 15 | img_needle = img_haystack[140:190, 220:270] # the template to search for 16 | img_sad = np.zeros(img_haystack.shape) # score image 17 | 18 | height_h, width_h = img_haystack.shape 19 | height_n, width_n = img_needle.shape 20 | 21 | # calculate score for each pixel 22 | # stop iterating over pixels when the whole template cannot any more (i.e. stop 23 | # at bottom and right border) 24 | for y in range(height_h - height_n): 25 | for x in range(width_h - width_n): 26 | patch = img_haystack[y:y+height_n, x:x+width_n] 27 | img_sad[y, x] = sad(img_needle, patch) 28 | img_sad = img_sad / np.max(img_sad) 29 | 30 | # add highest score to bottom and right borders 31 | img_sad[height_h-height_n:, :] = np.max(img_sad[0:height_h, 0:width_h]) 32 | img_sad[:, width_h-width_n:] = np.max(img_sad[0:height_h, 0:width_h]) 33 | 34 | # plot results 35 | util.plot_images_grayscale( 36 | [img_haystack, img_needle, img_sad], 37 | ["Image", "Image (Search Template)", "Matching (darkest = best match)"] 38 | ) 39 | 40 | def sad(img1, img2): 41 | """Calculate the sum of absolute differences between two image patches. 42 | Args: 43 | im1 Image patch 44 | im2 Image patch 45 | Results: 46 | Sum of absolute differences""" 47 | return np.sum(np.abs(img1 - img2)) 48 | 49 | if __name__ == "__main__": 50 | main() 51 | -------------------------------------------------------------------------------- /computer-vision/util.py: -------------------------------------------------------------------------------- 1 | """Utility functions for the CV algorithms.""" 2 | import matplotlib.pyplot as plt 3 | 4 | def plot_images_grayscale(images, titles, no_axis=False): 5 | """Plot a list of grayscale images in one window. 6 | Args: 7 | images List of images (2D arrays) 8 | titles List of titles for each image 9 | no_axis Whether to show x/y axis""" 10 | fig = plt.figure() 11 | for i, (image, title) in enumerate(zip(images, titles)): 12 | ax = fig.add_subplot(1,len(images),i+1) 13 | ax.set_title(title) 14 | if no_axis: 15 | ax.get_xaxis().set_visible(False) 16 | ax.get_yaxis().set_visible(False) 17 | plt.imshow(image, cmap=plt.cm.gray) 18 | plt.show() 19 | 20 | def quantize(val, to_values): 21 | """Quantizes a value to a set of allowed values. 22 | Compares to each value among the allowed values and thus is rather slow. 23 | Example: 24 | quantize(4.6, [3.5, 4, 4.5, 5, 5.5, 6]) -> 4.5 25 | 26 | Args: 27 | val The value to quantize. 28 | to_values The list of allowed values. 29 | Returns: 30 | integer or float (depending on to_values)""" 31 | best_match = None 32 | best_match_diff = None 33 | for other_val in to_values: 34 | diff = abs(other_val - val) 35 | if best_match is None or diff < best_match_diff: 36 | best_match = other_val 37 | best_match_diff = diff 38 | return best_match 39 | 40 | def quantize_idx(val, to_values): 41 | """Quantizes a value to a set of allowed values and returns the index of 42 | the quantized value. 43 | Compares to each value among the allowed values and thus is rather slow. 44 | Example: 45 | quantize(4.6, [3.5, 4, 4.5, 5, 5.5, 6]) -> 2 (because quantized to 4.5) 46 | 47 | Args: 48 | val The value to quantize. 49 | to_values The list of allowed values. 50 | Returns: 51 | integer""" 52 | best_match_idx = None 53 | best_match = None 54 | best_match_diff = None 55 | for i, other_val in enumerate(to_values): 56 | diff = abs(other_val - val) 57 | if best_match is None or diff < best_match_diff: 58 | best_match_idx = i 59 | best_match = other_val 60 | best_match_diff = diff 61 | return best_match_idx 62 | --------------------------------------------------------------------------------