├── LICENSE ├── .gitignore ├── README.md ├── photutils_detection_core.py └── pd.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Exclude meta files on MacOS 2 | *.DS_Store 3 | 4 | # Exclude input pictures 5 | *.raw 6 | *.png 7 | 8 | # Byte-compiled / optimized / DLL files 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | # C extensions 14 | *.so 15 | 16 | # Distribution / packaging 17 | .Python 18 | env/ 19 | build/ 20 | develop-eggs/ 21 | dist/ 22 | downloads/ 23 | eggs/ 24 | .eggs/ 25 | lib/ 26 | lib64/ 27 | parts/ 28 | sdist/ 29 | var/ 30 | wheels/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | .hypothesis/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # pyenv 81 | .python-version 82 | 83 | # celery beat schedule file 84 | celerybeat-schedule 85 | 86 | # SageMath parsed files 87 | *.sage.py 88 | 89 | # dotenv 90 | .env 91 | 92 | # virtualenv 93 | .venv 94 | venv/ 95 | ENV/ 96 | 97 | # Spyder project settings 98 | .spyderproject 99 | .spyproject 100 | 101 | # Rope project settings 102 | .ropeproject 103 | 104 | # mkdocs documentation 105 | /site 106 | 107 | # mypy 108 | .mypy_cache/ 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Parallelograms-Detection 2 | ## Project description: 3 | Parallelograms appear frequently in images that contain man-made objects. They often correspond to the projections of rectangular surfaces when viewed at an angle that is not perpendicular to the surfaces. In this project, you are to design and implement a program that can detect parallelograms of all sizes in an image. 4 | 5 | Your program will consist of three steps: 6 | 1. detect edges using the Sobel’s operator, 7 | 2. detect straight line segments using the Hough Transform, and 8 | 3. detect parallelograms from the straight-line segments detected in step (2). In step (1), compute edge magnitude using the formula below and then normalize the magnitude values to lie within the range [0,255]. Next, manually choose a threshold value to produce a binary edge map. 9 | 10 | Edge Magnitude = \sqrt{Gx^2 + Gy^2}(square root of G x squared plus G y squared end root, Gx and Gy are the horizontal and vertical gradients, respectively.) 11 | 12 | ## Input 13 | The test images that will be provided to you are in color so you will need to convert them into grayscale images by using the formula luminance = 0.30R + 0.59G + 0.11B, where R, G, and B, are the red, green, and blue components. Test images in both JPEG band RAW image formats will be provided. In the RAW image format, the red, green, and blue components of the pixels are recorded in an interleaved manner, occupying one byte per color component per pixel (See description below). The RAW image format does not contain any header bytes. 14 | 15 | Python, C/C++, Matlab and Java are the recommended programming languages. If you intend to use a different language, send me an email first. You are not allowed to use any built-in library functions for any of the steps that you are required to implement. 16 | 17 | 18 | :information_desk_person: 19 | :information_desk_person: 20 | :information_desk_person: 21 | :information_desk_person: 22 | :information_desk_person: 23 | :information_desk_person: 24 | 25 | * You are not allowed to use the Convolution or Cross-Correlation built-in library function of any programming language. You have to write your own function. 26 | * You are allowed to use Read, Write, and Display images library functions. 27 | * There are 3 sets of test images attached. Each image is available in both .jpg and .raw formats. See Project Description for a description of the .raw format. TestImage1c and Testimage2c are of dimension 756 rows x 1008 columns, and Testimage3 has dimension 413 rows x 550 columns. 28 | 29 | ## Follow up 30 | A optimized version of the implementation is [Image_Processing_Optimization](https://github.com/stella0316/Image_Processing_Optimization), in which techniques such as built-in functions, Numpy/vectorizing, Itertools, Cython, Numba, etc. are heavily utilized to speed it up, by [@stella0316](https://github.com/stella0316), [@Ksenia](https://github.com/ksenialearn) and [@googlr](https://github.com/googlr). 31 | -------------------------------------------------------------------------------- /photutils_detection_core.py: -------------------------------------------------------------------------------- 1 | # Refer to: 2 | # http://photutils.readthedocs.io/en/stable/_modules/photutils/detection/core.html#find_peaks 3 | 4 | 5 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 6 | """Functions for detecting sources in an astronomical image.""" 7 | 8 | from __future__ import (absolute_import, division, print_function, 9 | unicode_literals) 10 | 11 | import numpy as np 12 | # from astropy.stats import sigma_clipped_stats 13 | # from astropy.table import Column, Table 14 | 15 | # from ..utils.cutouts import cutout_footprint 16 | # from ..utils.wcs_helpers import pixel_to_icrs_coords 17 | 18 | 19 | # __all__ = ['detect_threshold', 'find_peaks'] 20 | 21 | 22 | # def detect_threshold(data, snr, background=None, error=None, mask=None, 23 | # mask_value=None, sigclip_sigma=3.0, sigclip_iters=None): 24 | # """ 25 | # Calculate a pixel-wise threshold image that can be used to detect 26 | # sources. 27 | 28 | # Parameters 29 | # ---------- 30 | # data : array_like 31 | # The 2D array of the image. 32 | 33 | # snr : float 34 | # The signal-to-noise ratio per pixel above the ``background`` for 35 | # which to consider a pixel as possibly being part of a source. 36 | 37 | # background : float or array_like, optional 38 | # The background value(s) of the input ``data``. ``background`` 39 | # may either be a scalar value or a 2D image with the same shape 40 | # as the input ``data``. If the input ``data`` has been 41 | # background-subtracted, then set ``background`` to ``0.0``. If 42 | # `None`, then a scalar background value will be estimated using 43 | # sigma-clipped statistics. 44 | 45 | # error : float or array_like, optional 46 | # The Gaussian 1-sigma standard deviation of the background noise 47 | # in ``data``. ``error`` should include all sources of 48 | # "background" error, but *exclude* the Poisson error of the 49 | # sources. If ``error`` is a 2D image, then it should represent 50 | # the 1-sigma background error in each pixel of ``data``. If 51 | # `None`, then a scalar background rms value will be estimated 52 | # using sigma-clipped statistics. 53 | 54 | # mask : array_like, bool, optional 55 | # A boolean mask with the same shape as ``data``, where a `True` 56 | # value indicates the corresponding element of ``data`` is masked. 57 | # Masked pixels are ignored when computing the image background 58 | # statistics. 59 | 60 | # mask_value : float, optional 61 | # An image data value (e.g., ``0.0``) that is ignored when 62 | # computing the image background statistics. ``mask_value`` will 63 | # be ignored if ``mask`` is input. 64 | 65 | # sigclip_sigma : float, optional 66 | # The number of standard deviations to use as the clipping limit 67 | # when calculating the image background statistics. 68 | 69 | # sigclip_iters : int, optional 70 | # The number of iterations to perform sigma clipping, or `None` to 71 | # clip until convergence is achieved (i.e., continue until the last 72 | # iteration clips nothing) when calculating the image background 73 | # statistics. 74 | 75 | # Returns 76 | # ------- 77 | # threshold : 2D `~numpy.ndarray` 78 | # A 2D image with the same shape as ``data`` containing the 79 | # pixel-wise threshold values. 80 | 81 | # See Also 82 | # -------- 83 | # :func:`photutils.segmentation.detect_sources` 84 | 85 | # Notes 86 | # ----- 87 | # The ``mask``, ``mask_value``, ``sigclip_sigma``, and 88 | # ``sigclip_iters`` inputs are used only if it is necessary to 89 | # estimate ``background`` or ``error`` using sigma-clipped background 90 | # statistics. If ``background`` and ``error`` are both input, then 91 | # ``mask``, ``mask_value``, ``sigclip_sigma``, and ``sigclip_iters`` 92 | # are ignored. 93 | # """ 94 | 95 | # if background is None or error is None: 96 | # data_mean, data_median, data_std = sigma_clipped_stats( 97 | # data, mask=mask, mask_value=mask_value, sigma=sigclip_sigma, 98 | # iters=sigclip_iters) 99 | # bkgrd_image = np.zeros_like(data) + data_mean 100 | # bkgrdrms_image = np.zeros_like(data) + data_std 101 | 102 | # if background is None: 103 | # background = bkgrd_image 104 | # else: 105 | # if np.isscalar(background): 106 | # background = np.zeros_like(data) + background 107 | # else: 108 | # if background.shape != data.shape: 109 | # raise ValueError('If input background is 2D, then it ' 110 | # 'must have the same shape as the input ' 111 | # 'data.') 112 | 113 | # if error is None: 114 | # error = bkgrdrms_image 115 | # else: 116 | # if np.isscalar(error): 117 | # error = np.zeros_like(data) + error 118 | # else: 119 | # if error.shape != data.shape: 120 | # raise ValueError('If input error is 2D, then it ' 121 | # 'must have the same shape as the input ' 122 | # 'data.') 123 | 124 | # return background + (error * snr) 125 | 126 | 127 | 128 | def find_peaks(data, threshold, box_size=3, footprint=None, mask=None, 129 | border_width=None, npeaks=np.inf, subpixel=False, error=None, 130 | wcs=None): 131 | """ 132 | Find local peaks in an image that are above above a specified 133 | threshold value. 134 | 135 | Peaks are the maxima above the ``threshold`` within a local region. 136 | The regions are defined by either the ``box_size`` or ``footprint`` 137 | parameters. ``box_size`` defines the local region around each pixel 138 | as a square box. ``footprint`` is a boolean array where `True` 139 | values specify the region shape. 140 | 141 | If multiple pixels within a local region have identical intensities, 142 | then the coordinates of all such pixels are returned. Otherwise, 143 | there will be only one peak pixel per local region. Thus, the 144 | defined region effectively imposes a minimum separation between 145 | peaks (unless there are identical peaks within the region). 146 | 147 | When using subpixel precision (``subpixel=True``), then a cutout of 148 | the specified ``box_size`` or ``footprint`` will be taken centered 149 | on each peak and fit with a 2D Gaussian (plus a constant). In this 150 | case, the fitted local centroid and peak value (the Gaussian 151 | amplitude plus the background constant) will also be returned in the 152 | output table. 153 | 154 | Parameters 155 | ---------- 156 | data : array_like 157 | The 2D array of the image. 158 | 159 | threshold : float or array-like 160 | The data value or pixel-wise data values to be used for the 161 | detection threshold. A 2D ``threshold`` must have the same 162 | shape as ``data``. See `detect_threshold` for one way to create 163 | a ``threshold`` image. 164 | 165 | box_size : scalar or tuple, optional 166 | The size of the local region to search for peaks at every point 167 | in ``data``. If ``box_size`` is a scalar, then the region shape 168 | will be ``(box_size, box_size)``. Either ``box_size`` or 169 | ``footprint`` must be defined. If they are both defined, then 170 | ``footprint`` overrides ``box_size``. 171 | 172 | footprint : `~numpy.ndarray` of bools, optional 173 | A boolean array where `True` values describe the local footprint 174 | region within which to search for peaks at every point in 175 | ``data``. ``box_size=(n, m)`` is equivalent to 176 | ``footprint=np.ones((n, m))``. Either ``box_size`` or 177 | ``footprint`` must be defined. If they are both defined, then 178 | ``footprint`` overrides ``box_size``. 179 | 180 | mask : array_like, bool, optional 181 | A boolean mask with the same shape as ``data``, where a `True` 182 | value indicates the corresponding element of ``data`` is masked. 183 | 184 | border_width : bool, optional 185 | The width in pixels to exclude around the border of the 186 | ``data``. 187 | 188 | npeaks : int, optional 189 | The maximum number of peaks to return. When the number of 190 | detected peaks exceeds ``npeaks``, the peaks with the highest 191 | peak intensities will be returned. 192 | 193 | subpixel : bool, optional 194 | If `True`, then a cutout of the specified ``box_size`` or 195 | ``footprint`` will be taken centered on each peak and fit with a 196 | 2D Gaussian (plus a constant). In this case, the fitted local 197 | centroid and peak value (the Gaussian amplitude plus the 198 | background constant) will also be returned in the output table. 199 | 200 | error : array_like, optional 201 | The 2D array of the 1-sigma errors of the input ``data``. 202 | ``error`` is used only to weight the 2D Gaussian fit performed 203 | when ``subpixel=True``. 204 | 205 | wcs : `~astropy.wcs.WCS` 206 | The WCS transformation to use to convert from pixel coordinates 207 | to ICRS world coordinates. If `None`, then the world 208 | coordinates will not be returned in the output 209 | `~astropy.table.Table`. 210 | 211 | Returns 212 | ------- 213 | output : `~astropy.table.Table` 214 | A table containing the x and y pixel location of the peaks and 215 | their values. If ``subpixel=True``, then the table will also 216 | contain the local centroid and fitted peak value. 217 | """ 218 | 219 | from scipy import ndimage 220 | 221 | if np.all(data == data.flat[0]): 222 | return [] 223 | 224 | if footprint is not None: 225 | data_max = ndimage.maximum_filter(data, footprint=footprint, 226 | mode='constant', cval=0.0) 227 | else: 228 | data_max = ndimage.maximum_filter(data, size=box_size, 229 | mode='constant', cval=0.0) 230 | 231 | peak_goodmask = (data == data_max) # good pixels are True 232 | 233 | if mask is not None: 234 | mask = np.asanyarray(mask) 235 | if data.shape != mask.shape: 236 | raise ValueError('data and mask must have the same shape') 237 | peak_goodmask = np.logical_and(peak_goodmask, ~mask) 238 | 239 | if border_width is not None: 240 | for i in range(peak_goodmask.ndim): 241 | peak_goodmask = peak_goodmask.swapaxes(0, i) 242 | peak_goodmask[:border_width] = False 243 | peak_goodmask[-border_width:] = False 244 | peak_goodmask = peak_goodmask.swapaxes(0, i) 245 | 246 | peak_goodmask = np.logical_and(peak_goodmask, (data > threshold)) 247 | y_peaks, x_peaks = peak_goodmask.nonzero() 248 | peak_values = data[y_peaks, x_peaks] 249 | 250 | if len(x_peaks) > npeaks: 251 | idx = np.argsort(peak_values)[::-1][:npeaks] 252 | x_peaks = x_peaks[idx] 253 | y_peaks = y_peaks[idx] 254 | peak_values = peak_values[idx] 255 | 256 | if subpixel: 257 | from ..centroids import fit_2dgaussian # prevents circular import 258 | 259 | x_centroid, y_centroid = [], [] 260 | fit_peak_values = [] 261 | for (y_peak, x_peak) in zip(y_peaks, x_peaks): 262 | rdata, rmask, rerror, slc = cutout_footprint( 263 | data, (x_peak, y_peak), box_size=box_size, 264 | footprint=footprint, mask=mask, error=error) 265 | gaussian_fit = fit_2dgaussian(rdata, mask=rmask, error=rerror) 266 | if gaussian_fit is None: 267 | x_cen, y_cen, fit_peak_value = np.nan, np.nan, np.nan 268 | else: 269 | x_cen = slc[1].start + gaussian_fit.x_mean.value 270 | y_cen = slc[0].start + gaussian_fit.y_mean.value 271 | fit_peak_value = (gaussian_fit.constant.value + 272 | gaussian_fit.amplitude.value) 273 | x_centroid.append(x_cen) 274 | y_centroid.append(y_cen) 275 | fit_peak_values.append(fit_peak_value) 276 | 277 | columns = (x_peaks, y_peaks, peak_values, x_centroid, y_centroid, 278 | fit_peak_values) 279 | names = ('x_peak', 'y_peak', 'peak_value', 'x_centroid', 'y_centroid', 280 | 'fit_peak_value') 281 | else: 282 | columns = (x_peaks, y_peaks, peak_values) 283 | names = ('x_peak', 'y_peak', 'peak_value') 284 | 285 | # table = Table(columns, names=names) 286 | table = columns 287 | 288 | if wcs is not None: 289 | icrs_ra_peak, icrs_dec_peak = pixel_to_icrs_coords(x_peaks, y_peaks, 290 | wcs) 291 | table.add_column(Column(icrs_ra_peak, name='icrs_ra_peak'), index=2) 292 | table.add_column(Column(icrs_dec_peak, name='icrs_dec_peak'), index=3) 293 | 294 | if subpixel: 295 | icrs_ra_centroid, icrs_dec_centroid = pixel_to_icrs_coords( 296 | x_centroid, y_centroid, wcs) 297 | idx = table.colnames.index('y_centroid') 298 | table.add_column(Column(icrs_ra_centroid, 299 | name='icrs_ra_centroid'), index=idx+1) 300 | table.add_column(Column(icrs_dec_centroid, 301 | name='icrs_dec_centroid'), index=idx+2) 302 | 303 | return table 304 | -------------------------------------------------------------------------------- /pd.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # The program will consist of three steps: 4 | # (1) detect edges using the Sobel’s operator, 5 | # (2) detect straight line segments using the Hough Transform, and 6 | # (3) detect parallelograms from the straight-line segments detected in step (2). 7 | # In step (1), compute edge magnitude using the formula below and 8 | # then normalize the magnitude values to lie within the range [0,255]. 9 | # Next, manually choose a threshold value to produce a binary edge map. 10 | 11 | import numpy as np 12 | import matplotlib 13 | from matplotlib import pylab as plt 14 | import math 15 | import itertools as it 16 | # import cv2 17 | # from skimage.feature import peak_local_max 18 | from photutils_detection_core import find_peaks 19 | # import sys 20 | 21 | 22 | row, col = 756, 1008 # Size of TestImage 1 and 2 23 | # row, col = 413, 550 # Size of TestImage 3 24 | filename = "TestImage1.raw" 25 | T = 25 # Threshold in the normalized gradient magnitue 26 | Canny_Edge_Detector_threshold = 10 27 | local_maxima_window_size = 3 # neighborhood_size 28 | # the de-Houghed image (using a relative threshold of 40%) 29 | relative_threshold_ratio = 0.4 30 | distance_threshold = 8 # Threshold distance to determin if a point on a line 31 | 32 | # the least points on line to be considered to be a valid line 33 | points_on_line_threshold = 20 34 | 35 | # convert them into grayscale images by using the formula 36 | # luminance = 0.30R + 0.59G + 0.11B, 37 | # where R, G, and B, are the red, green, and blue components. 38 | 39 | 40 | def cvt2grayscale(img): 41 | grayImage = [] 42 | for i in range(0, img.size // 3): 43 | luminance = int(0.3 * img[3 * i] + 0.59 * 44 | img[3 * i + 1] + 0.11 * img[3 * i + 2]) 45 | grayImage.append(luminance) 46 | 47 | return np.array(grayImage) 48 | 49 | # Gausssion smoothing: https://homepages.inf.ed.ac.uk/rbf/HIPR2/gsmooth.htm 50 | 51 | 52 | def smooth_image_with_Gaussian_filter(img): 53 | kernel = (0.006, 0.061, 0.242, 0.383, 0.242, 0.061, 0.006) 54 | kernel_size = len(kernel) 55 | border_offset = (kernel_size - 1) // 2 56 | 57 | img_copy = np.copy(img) 58 | for i in range(0, row): 59 | # Keep border values as they are 60 | for j in range(border_offset, col - border_offset): 61 | img_copy_ij = 0 62 | for k in range((-1) * border_offset, border_offset + 1): 63 | img_copy_ij += img[i][j + k] * kernel[border_offset + k] 64 | img_copy[i][j] = img_copy_ij 65 | 66 | img_copy_copy = np.copy(img_copy) 67 | # Keep border values as they are 68 | for i in range(border_offset, row - border_offset): 69 | for j in range(0, col): 70 | img_copy_copy_ij = 0 71 | for k in range((-1) * border_offset, border_offset + 1): 72 | img_copy_copy_ij += img_copy[i + 73 | k][j] * kernel[border_offset + k] 74 | img_copy_copy[i][j] = img_copy_copy_ij 75 | 76 | return img_copy_copy 77 | 78 | 79 | def sobels_operator(img): 80 | mag = [] 81 | img_row, img_col = img.shape 82 | for i in range(1, img_row - 1): 83 | for j in range(1, img_col - 1): 84 | # compute edge magnitude using the formula 85 | g_x = (img[i - 1][j + 1] + 2 * img[i][j + 1] + img[i + 1][j + 1] 86 | - img[i - 1][j - 1] - 2 * img[i][j - 1] - img[i + 1][j - 1]) 87 | g_y = (img[i - 1][j - 1] + 2 * img[i - 1][j] + img[i - 1][j + 1] 88 | - img[i + 1][j - 1] - 2 * img[i + 1][j] - img[i + 1][j + 1]) 89 | mag_i_j = math.sqrt(g_x * g_x + g_y * g_y) 90 | mag.append(int(mag_i_j)) 91 | 92 | # normalize the magnitude values to lie within the range [0,255]. 93 | min_mag = min(mag) 94 | max_mag = max(mag) 95 | mag_normalized = [] 96 | for val in mag: 97 | mag_normalized.append(int((val - min_mag) * 255 / (max_mag - min_mag))) 98 | # Save the normalized gradient magnitue 99 | normalized_gradient_magnitue_as_a_image = np.array( 100 | mag_normalized).reshape([img_row - 2, img_col - 2]) 101 | plt.imshow(normalized_gradient_magnitue_as_a_image, cmap='gray') 102 | # plt.show() 103 | plt.savefig("normalized_gradient_magnitue_as_a_image.png") 104 | plt.close() 105 | 106 | # filter: threshold T=225 107 | mag_normalized_filtered = [] 108 | for val in mag_normalized: 109 | mag_normalized_filtered.append(val if val >= T else 0) 110 | #mag_i_j = mag_i_j if mag_i_j >= 225 else 0 111 | #mag_i_j = mag_i_j if mag_i_j <= 225 else 255 112 | return np.array(mag_normalized_filtered).reshape([img_row - 2, img_col - 2]) 113 | 114 | 115 | # Read Image 116 | testImage = np.fromfile(filename, dtype='uint8', sep="") 117 | 118 | # Convert to grayscale image 119 | grayImage = cvt2grayscale(testImage).reshape([row, col]) 120 | print("Step 1: Convert image to grayscale.") 121 | # print grayImage.shape 122 | 123 | # Smooth_image_with_Gaussian_filter 124 | grayImage_smoothed = smooth_image_with_Gaussian_filter(grayImage) 125 | # Display Image 126 | plt.imshow(grayImage_smoothed, cmap='gray') 127 | # plt.show() 128 | plt.savefig("grayImage_smoothed_with_Gaussian_filter.png") 129 | plt.close() 130 | 131 | # Compute gradient magnitude and gradient angle 132 | gradient_magnitude = np.zeros((row, col), dtype='uint8') 133 | gradient_angle = np.zeros((row, col), dtype='uint8') 134 | quantize_angle_of_the_gradient = np.zeros((row, col), dtype='uint8') 135 | 136 | 137 | def quantize_angle_of_the_gradient_to_four_sectors(angle): 138 | # Double check the parameter 139 | if (angle < 0 or angle > 360): 140 | print("Warning: invalid parameter in quantize_angle_of_the_gradient_to_four_sectors(angle).") 141 | return 4 142 | if (angle <= 0 + 22.5 or 143 | (angle >= 180 - 22.5 and angle <= 180 + 22.5) or 144 | angle >= 315 + 22.5): 145 | return 0 146 | if ((angle > 45 - 22.5 and angle < 45 + 22.5) or 147 | (angle > 225 - 22.5 and angle < 225 + 22.5)): 148 | return 1 149 | if ((angle >= 90 - 22.5 and angle <= 90 + 22.5) or 150 | (angle >= 270 - 22.5 and angle <= 270 + 22.5)): 151 | return 2 152 | if ((angle > 135 - 22.5 and angle < 135 + 22.5) or 153 | (angle > 315 - 22.5 and angle < 315 + 22.5)): 154 | return 3 155 | 156 | 157 | def compute_gradient_magnitude_and_gradient_angle(image_smoothed): 158 | for i in range(1, row): 159 | for j in range(1, col): 160 | Gx = (image_smoothed[i][j] + image_smoothed[i - 1][j] 161 | - image_smoothed[i][j - 1] - image_smoothed[i - 1][j - 1]) 162 | Gy = (image_smoothed[i - 1][j - 1] + image_smoothed[i - 1][j] 163 | - image_smoothed[i][j - 1] - image_smoothed[i][j]) 164 | gradient_magnitude[i][j] = math.sqrt(Gx * Gx + Gy * Gy) 165 | if Gx == 0: 166 | gradient_angle[i][j] = 90 if Gy > 0 else 270 167 | else: 168 | gradient_angle[i][j] = math.degrees(math.atan2(Gy, Gx)) 169 | 170 | quantize_angle_of_the_gradient[i][ 171 | j] = quantize_angle_of_the_gradient_to_four_sectors(gradient_angle[i][j]) 172 | 173 | compute_gradient_magnitude_and_gradient_angle(grayImage_smoothed) 174 | # Non-maxima Suppression 175 | # Thin magnitude image by using a 3×3 window 176 | for i in range(1, row - 1): 177 | for j in range(1, col - 1): 178 | sector_ij = quantize_angle_of_the_gradient[i][j] 179 | if sector_ij == 0: 180 | gradient_magnitude[i][j] = gradient_magnitude[i][j] if (gradient_magnitude[i][j] >= gradient_magnitude[ 181 | i][j - 1] and gradient_magnitude[i][j] >= gradient_magnitude[i][j + 1]) else 0 182 | elif sector_ij == 1: 183 | gradient_magnitude[i][j] = gradient_magnitude[i][j] if (gradient_magnitude[i][j] >= gradient_magnitude[ 184 | i - 1][j + 1] and gradient_magnitude[i][j] >= gradient_magnitude[i + 1][j - 1]) else 0 185 | elif sector_ij == 2: 186 | gradient_magnitude[i][j] = gradient_magnitude[i][j] if (gradient_magnitude[i][j] >= gradient_magnitude[ 187 | i - 1][j] and gradient_magnitude[i][j] >= gradient_magnitude[i + 1][j]) else 0 188 | elif sector_ij == 3: 189 | gradient_magnitude[i][j] = gradient_magnitude[i][j] if (gradient_magnitude[i][j] >= gradient_magnitude[ 190 | i - 1][j - 1] and gradient_magnitude[i][j] >= gradient_magnitude[i + 1][j + 1]) else 0 191 | else: 192 | print("Warning: invalid sector in Non-maxima Suppression.") 193 | 194 | for i in range(1, row - 1): 195 | for j in range(1, col - 1): 196 | gradient_magnitude[i][j] = gradient_magnitude[i][ 197 | j] if gradient_magnitude[i][j] >= Canny_Edge_Detector_threshold else 0 198 | 199 | print("Step 2: Canny Edge Detecter applied.") 200 | plt.imshow(gradient_magnitude, cmap='gray') 201 | # plt.show() 202 | plt.savefig("edges_detected_by_Canny_Edge_Detector.png") 203 | plt.close() 204 | 205 | 206 | ################################################################# 207 | #(1) detect edges using the Sobel’s operator 208 | #– Filtering 209 | #– Enhancement 210 | # imgMag = sobels_operator(grayImage) 211 | # print("Step 2: Sobel's operator applied.") 212 | # plt.imshow(imgMag, cmap = 'gray') 213 | # #plt.show() 214 | # plt.savefig("edges_detected_in_image.png") 215 | # plt.close() 216 | 217 | imgMag = gradient_magnitude 218 | 219 | ################################################################# 220 | #(2) detect straight line segments using the Hough Transform 221 | theta_step_size = 3 222 | p_step_size = 1 223 | theta_MAX_VALUE = 360 224 | p_MAX_VALUE = int(math.sqrt(row * row + col * col)) 225 | accumulator_array = np.zeros( 226 | (theta_MAX_VALUE // theta_step_size + 1, p_MAX_VALUE // p_step_size + 1), dtype='uint8') 227 | # Compute the accumulator array 228 | imgMag_row, imgMag_col = imgMag.shape 229 | for i in range(0, imgMag_row): 230 | for j in range(0, imgMag_col): 231 | if(imgMag[i][j] > 0): 232 | # p = x*cos(theta) + y*sin(theta) 233 | theta = 0 234 | while theta < 360: 235 | theta_radians = math.radians(theta + theta_step_size / 2.0) 236 | p_estimate = i * math.cos(theta_radians) + \ 237 | j * math.sin(theta_radians) 238 | # Update the accumulator array 239 | accu_x = theta // theta_step_size 240 | accu_y = int(p_estimate / p_step_size) 241 | accumulator_array[accu_x][accu_y] += 1 242 | # next theta 243 | theta = theta + theta_step_size 244 | 245 | max_accumulator = np.amax(accumulator_array) 246 | print(max_accumulator) 247 | print("Step 3: Hough Transform applied.") 248 | # plt.imshow(accumulator_array, cmap='gray') 249 | # plt.show() 250 | # plt.close() 251 | 252 | 253 | ################################################################# 254 | #(3) detect parallelograms from the straight-line segments detected in step (2). 255 | # the de-Houghed image (using a relative threshold of 40%) 256 | accu_row, accu_col = accumulator_array.shape 257 | peak_list = [] 258 | 259 | # Relative threshold filtering 260 | relative_threshold = max_accumulator * relative_threshold_ratio 261 | for i in range(0, accu_row): 262 | for j in range(0, accu_col): 263 | # apply the threshold filter 264 | accumulator_i_j = accumulator_array[i][j] 265 | accumulator_array[i][ 266 | j] = accumulator_i_j if accumulator_i_j >= relative_threshold else 0 267 | # if accumulator_i_j >= relative_threshold: 268 | # peak_p = (j + 0.5) * p_step_size 269 | # peak_theta = (i + 0.5) * theta_step_size 270 | # peak_list.append([peak_theta, peak_p]) 271 | 272 | # plt.imshow(accumulator_array, cmap='gray') 273 | # plt.show() 274 | # plt.close() 275 | 276 | table = find_peaks(accumulator_array, relative_threshold) 277 | # print(table) 278 | peaks_found = [] 279 | for i in range(0, len(table[0])): 280 | table_x = table[1][i] 281 | table_y = table[0][i] 282 | peaks_found.append([(table_x + 0.5) * theta_step_size, 283 | (table_y + 0.5) * p_step_size]) 284 | # print( accumulator_array[ table_x ][ table_y ] ) 285 | 286 | print(peaks_found) 287 | 288 | 289 | # Using local-maxima threshold 290 | # With a threshold window of 3x3 291 | window_size = local_maxima_window_size 292 | 293 | 294 | def xy_in_range_of_accumulator_array(x, y): 295 | accu_arr_row, accu_arr_col = accumulator_array.shape 296 | return True if (x >= 0 and x < accu_arr_row and y >= 0 and y < accu_arr_col) else False 297 | 298 | 299 | def accumulator_is_local_maxima(i, j): 300 | if accumulator_array[i][j] == 0: # already surpressed 301 | return False 302 | for s_i in range((-1) * window_size, window_size + 1): 303 | for s_j in range((-1) * window_size, window_size + 1): 304 | local_x = i + s_i 305 | local_y = j + s_j 306 | if xy_in_range_of_accumulator_array(local_x, local_y): 307 | # Notice that there might be more than one maxima 308 | if accumulator_array[i][j] < accumulator_array[local_x][local_y]: 309 | return False 310 | return True 311 | 312 | for i in range(0, accu_row): 313 | for j in range(0, accu_col): 314 | # apply the threshold filter 315 | if accumulator_is_local_maxima(i, j): 316 | peak_p = (j + 0.5) * p_step_size 317 | peak_theta = (i + 0.5) * theta_step_size 318 | peak_list.append([peak_theta, peak_p]) 319 | 320 | 321 | # def accumulator_is_local_maxima( i, j ): 322 | # if accumulator_array[i][j] == 0: # already surpressed 323 | # return False 324 | # for s_i in range( (-1)*window_size, window_size + 1 ): 325 | # for s_j in range( (-1)*window_size, window_size + 1 ): 326 | # if accumulator_array[i][j] < accumulator_array[ i + s_i ][ j + s_j ]: # Notice that there might be more than one maxima 327 | # return False 328 | # return True 329 | 330 | # for i in range( window_size, accu_row - window_size): 331 | # for j in range( window_size, accu_col - window_size): 332 | # #apply the threshold filter 333 | # if accumulator_is_local_maxima( i, j ): 334 | # peak_p = (j + 0.5) * p_step_size 335 | # peak_theta = (i + 0.5) * theta_step_size 336 | # peak_list.append([peak_theta, peak_p]) 337 | 338 | print("peak_list: ") 339 | print(peak_list) 340 | 341 | peak_list = peaks_found 342 | 343 | ########################################################################## 344 | # Filter overlaping lines 345 | filter_step_size = theta_step_size 346 | 347 | # Compute average of a list of int 348 | 349 | 350 | def average_p(p_filter_list): 351 | list_len = len(p_filter_list) 352 | if list_len == 0: 353 | print("Warning: empty list.") 354 | p_sum = 0.0 355 | for p in p_filter_list: 356 | p_sum = p_sum + p 357 | 358 | return p_sum / list_len 359 | 360 | # Cluster a list of int to clustered list 361 | 362 | 363 | def cluster_list(p_list): 364 | p_list = sorted(p_list) 365 | list_len = len(p_list) 366 | clustered_list = [] 367 | if list_len == 0: 368 | return clustered_list 369 | p_val = p_list[0] 370 | p_filter_list = [] 371 | for i in range(0, list_len): 372 | if math.fabs(p_val - p_list[i]) < filter_step_size: 373 | p_filter_list.append(p_list[i]) 374 | else: 375 | p_new_average = average_p(p_filter_list) 376 | clustered_list.append(p_new_average) 377 | # update p_val and clear p_filter_list 378 | p_val = p_list[i] 379 | p_filter_list[:] = [] 380 | p_filter_list.append(p_list[i]) 381 | 382 | # clear p_filter_list 383 | if len(p_filter_list) != 0: 384 | p_new_average = average_p(p_filter_list) 385 | clustered_list.append(p_new_average) 386 | return clustered_list 387 | 388 | 389 | # Use dictionary to filter peaks 390 | peak_dict = {} 391 | for line in peak_list: 392 | if line[0] in peak_dict: 393 | # append the new number to the existing array at this slot 394 | peak_dict[line[0]].append(line[1]) 395 | else: 396 | # create a new array in this slot 397 | peak_dict[line[0]] = [line[1]] 398 | 399 | for key in peak_dict: 400 | peak_dict[key] = cluster_list(peak_dict[key]) 401 | 402 | peak_list_filtered = [] 403 | for key in peak_dict: 404 | for val in peak_dict[key]: 405 | peak_list_filtered.append([key, val]) 406 | 407 | print("peak_list_filtered: ") 408 | print(peak_list_filtered) 409 | peak = np.array(peak_list_filtered) 410 | 411 | ########################################################################## 412 | # print(peak) 413 | edge_map = np.zeros((row, col), dtype='uint8') 414 | # Initialize to edge map to 255 415 | for i in range(0, row): 416 | for j in range(0, col): 417 | edge_map[i][j] = 255 418 | 419 | # Copy the magnitude array imgMag to edge_map 420 | for i in range(0, row - 2): 421 | for j in range(0, col - 2): 422 | if imgMag[i][j] > 0: 423 | edge_map[i + 1][j + 1] = 0 424 | 425 | 426 | def xy_in_range(x, y): 427 | return True if (x >= 0 and x < row and y >= 0 and y < col) else False 428 | 429 | 430 | def draw_line(i_theta, i_p): 431 | # Draw the lines in edge_map 432 | i_theta_radians = math.radians(i_theta) 433 | if (i_theta == 0 or i_theta == 180): 434 | i_x = i_p / math.cos(i_theta_radians) 435 | for j in range(0, col): 436 | if xy_in_range(i_x, j): 437 | edge_map[i_x][j] = 0 438 | else: 439 | for i_x in range(0, row): 440 | i_y = int((i_p - i_x * math.cos(i_theta_radians)) / 441 | math.sin(i_theta_radians)) 442 | if xy_in_range(i_x, i_y): 443 | edge_map[i_x][i_y] = 0 444 | 445 | 446 | # Draw the lines in edge_map 447 | # print("Peak includes:") 448 | # print( peak ) 449 | for line in peak_list_filtered: 450 | draw_line(line[0], line[1]) 451 | plt.imshow(edge_map, cmap="gray") 452 | # plt.show() 453 | plt.savefig("image_with_all_straight_lines_detected.png") 454 | plt.close() 455 | 456 | 457 | # sys.exit() 458 | 459 | ########################################################################## 460 | # Extract line segments 461 | 462 | def get_bias_key_list_in_peak_dict(key): 463 | bias_keys = [key] 464 | peak_dict_keys = peak_dict.keys() 465 | key1 = key 466 | while (key1 + theta_step_size) in peak_dict_keys: 467 | key1 = key1 + theta_step_size 468 | bias_keys.append(key1) 469 | 470 | key2 = key 471 | while (key2 - theta_step_size) in peak_dict_keys: 472 | key2 = key2 - theta_step_size 473 | bias_keys.append(key2) 474 | 475 | return bias_keys 476 | 477 | # Correct bias of in Theta by allowing fluctions in theta when generating 478 | # parallel line pairs 479 | parallel_peak_dict = {} 480 | for key in peak_dict: 481 | bias_key_list = get_bias_key_list_in_peak_dict(key) 482 | # Use the min_key to represent the similar keys 483 | min_key = min(bias_key_list) 484 | parallel_peak_dict[min_key] = [] 485 | for bias_key in bias_key_list: 486 | bias_key_val_list = peak_dict[bias_key] 487 | for bias_key_val in bias_key_val_list: 488 | parallel_peak_dict[min_key].append((bias_key, bias_key_val)) 489 | 490 | # print("parallel_peak_dict:") 491 | # print( parallel_peak_dict ) 492 | # Compute possible parallelogram options 493 | para_gram_options = [] 494 | para_keys = list(it.combinations(parallel_peak_dict.keys(), 2)) 495 | for key in para_keys: 496 | key1, key2 = key 497 | key1_list = list(it.combinations(parallel_peak_dict[key1], 2)) 498 | key2_list = list(it.combinations(parallel_peak_dict[key2], 2)) 499 | for comb1 in key1_list: 500 | for comb2 in key2_list: 501 | theta1 = comb1[0][0] 502 | p1 = comb1[0][1] 503 | theta2 = comb1[1][0] 504 | p2 = comb1[1][1] 505 | theta3 = comb2[0][0] 506 | p3 = comb2[0][1] 507 | theta4 = comb2[1][0] 508 | p4 = comb2[1][1] 509 | para_gram_options.append( 510 | (theta1, p1, theta2, p2, theta3, p3, theta4, p4)) 511 | 512 | # print("para_gram_options:") 513 | # print( para_gram_options ) 514 | 515 | # Compute valid parallelogram 516 | 517 | # Get a copy of imgMag 518 | mag_map_copy = np.zeros((row, col), dtype='uint8') 519 | # Initialize to edge map to 255 520 | for i in range(0, row): 521 | for j in range(0, col): 522 | mag_map_copy[i][j] = 255 523 | # Copy the magnitude array imgMag to mag_map_copy 524 | for i in range(0, row - 2): 525 | for j in range(0, col - 2): 526 | if imgMag[i][j] > 0: 527 | mag_map_copy[i + 1][j + 1] = 0 528 | 529 | 530 | def sketch_dot_on_map(x, y, dot_map, sketch_val): 531 | dot_size = 5 532 | if xy_in_range(x, y): 533 | for i in range((-1) * dot_size, dot_size + 1): 534 | for j in range((-1) * dot_size, dot_size + 1): 535 | x_ij = i + x # (x,y) with offset i, j 536 | y_ij = j + y 537 | if xy_in_range(x_ij, y_ij): 538 | # print("sketch") 539 | dot_map[int(x_ij)][int(y_ij)] = sketch_val 540 | 541 | # Compute the intersection of two lines 542 | 543 | 544 | def intersection(theta1, p1, theta2, p2): 545 | theta1_radians = math.radians(theta1) 546 | theta2_radians = math.radians(theta2) 547 | x = (p2 * math.sin(theta1_radians) - p1 * math.sin(theta2_radians)) / \ 548 | math.sin(theta1_radians - theta2_radians) 549 | y = (p1 * math.cos(theta2_radians) - p2 * math.cos(theta1_radians)) / \ 550 | math.sin(theta1_radians - theta2_radians) 551 | # test_sketch_dot(x,y) #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 552 | return [x, y] 553 | 554 | 555 | def get_y_from_x(i_theta, i_p, i_x): 556 | i_theta_radians = math.radians(i_theta) 557 | if (i_theta == 0 or i_theta == 180): 558 | return 0 # special case, but does not matter 559 | else: 560 | i_y = int((i_p - i_x * math.cos(i_theta_radians)) / 561 | math.sin(i_theta_radians)) 562 | return i_y 563 | 564 | 565 | def near_edge_line(x, y): 566 | if xy_in_range(x, y): 567 | for i in range((-1) * distance_threshold, distance_threshold + 1): 568 | for j in range((-1) * distance_threshold, distance_threshold + 1): 569 | x_ij = i + x # (x,y) with offset i, j 570 | y_ij = j + y 571 | if xy_in_range(x_ij, y_ij): 572 | if mag_map_copy[x_ij][y_ij] == 0: 573 | return True 574 | else: 575 | continue 576 | return False 577 | else: 578 | return False 579 | 580 | # Count the number of point on (theta1, p1_1) restricted by (theta2, p2_1) 581 | # and (theta2, p2_2) 582 | 583 | 584 | def counting_points_on_line_segment(theta1, p1, theta3, p3, theta4, p4): 585 | x1, y1 = intersection(theta1, p1, theta3, p3) 586 | x2, y2 = intersection(theta1, p1, theta4, p4) 587 | # test_sketch_dot(x1,y1) 588 | # test_sketch_dot(x2,y2) 589 | # plt.imshow(mag_map_copy, cmap='gray') 590 | # plt.show() 591 | # plt.close() 592 | # print([x1,y1,x2,y2]) 593 | points_count = 0 594 | if xy_in_range(x1, y1) and xy_in_range(x2, y2): 595 | start_x = int(min(x1, x2)) 596 | end_x = int(max(x1, x2)) 597 | for x in range(start_x, end_x): 598 | y = get_y_from_x(theta1, p1, x) 599 | if near_edge_line(x, y): 600 | points_count = points_count + 1 601 | return points_count 602 | else: 603 | return 0 604 | 605 | 606 | def draw_parallelogram(line): 607 | draw_line(line[0], line[1]) 608 | draw_line(line[2], line[3]) 609 | draw_line(line[4], line[5]) 610 | draw_line(line[6], line[7]) 611 | 612 | 613 | def valid_parallelogram(line): 614 | # print("Validating parallelogram:") 615 | # print( line ) 616 | # draw_parallelogram( line ) 617 | # plt.imshow(edge_map, cmap='gray') 618 | # plt.show() 619 | # plt.close() 620 | 621 | if len(line) != 8: 622 | print("Warning: invalid data in valid_parallelogram().") 623 | theta1 = line[0] 624 | p1 = line[1] 625 | theta2 = line[2] 626 | p2 = line[3] 627 | theta3 = line[4] 628 | p3 = line[5] 629 | theta4 = line[6] 630 | p4 = line[7] 631 | points_line1 = counting_points_on_line_segment( 632 | theta1, p1, theta3, p3, theta4, p4) 633 | # draw_line( theta1,p1 ) 634 | # draw_line( theta3,p3 ) 635 | # draw_line( theta4,p4 ) 636 | # plt.imshow(edge_map, cmap='gray') 637 | # plt.show() 638 | # plt.close() 639 | # print("Points on line:") 640 | # print( points_line1 ) 641 | points_line2 = counting_points_on_line_segment( 642 | theta2, p2, theta3, p3, theta4, p4) 643 | points_line3 = counting_points_on_line_segment( 644 | theta3, p3, theta1, p1, theta2, p2) 645 | points_line4 = counting_points_on_line_segment( 646 | theta4, p4, theta1, p1, theta2, p2) 647 | 648 | if points_line1 > points_on_line_threshold and points_line2 > points_on_line_threshold and points_line3 > points_on_line_threshold and points_line4 > points_on_line_threshold: 649 | return points_line1 + points_line2 + points_line3 + points_line4 650 | else: # There is no enough points on at least one line segment 651 | return 0 652 | 653 | # Mask of parallelograms 654 | # 1 is not on parallelograms, 0 is on parallelograms 655 | mask_parallelogram = np.ones((row, col), dtype='uint8') 656 | 657 | 658 | # add line mask from (x1,y1) to (x2,y2) on line( i_theta, i_p) 659 | def add_line_mask(i_theta, i_p, x1, y1, x2, y2): 660 | x_min = int(min([x1, x2])) 661 | x_max = int(max([x1, x2])) 662 | # Draw the lines in mask 663 | i_theta_radians = math.radians(i_theta) 664 | if (i_theta == 0 or i_theta == 180): # x1 == x2 665 | y_min = int(min([y1, y2])) 666 | y_max = int(max([y1, y2])) 667 | i_x = x_min 668 | for j in range(y_min, y_max + 1): 669 | if xy_in_range(i_x, j): 670 | mask_parallelogram[i_x][j] = 0 671 | else: 672 | for i_x in range(x_min, x_max): 673 | i_y = int((i_p - i_x * math.cos(i_theta_radians)) / 674 | math.sin(i_theta_radians)) 675 | if xy_in_range(i_x, i_y): 676 | mask_parallelogram[i_x][i_y] = 0 677 | 678 | 679 | def add_parallelogram_mask(line): 680 | theta1 = line[0] 681 | p1 = line[1] 682 | theta2 = line[2] 683 | p2 = line[3] 684 | theta3 = line[4] 685 | p3 = line[5] 686 | theta4 = line[6] 687 | p4 = line[7] 688 | x1, y1 = intersection(theta1, p1, theta3, p3) 689 | x2, y2 = intersection(theta2, p2, theta3, p3) 690 | x3, y3 = intersection(theta2, p2, theta4, p4) 691 | x4, y4 = intersection(theta1, p1, theta4, p4) 692 | add_line_mask(theta3, p3, x1, y1, x2, y2) 693 | add_line_mask(theta2, p3, x2, y2, x3, y3) 694 | add_line_mask(theta4, p4, x3, y3, x4, y4) 695 | add_line_mask(theta1, p1, x4, y4, x1, y1) 696 | # Sketch on end points 697 | sketch_dot_on_map(x1, y1, mask_parallelogram, 0) 698 | sketch_dot_on_map(x2, y2, mask_parallelogram, 0) 699 | sketch_dot_on_map(x3, y3, mask_parallelogram, 0) 700 | sketch_dot_on_map(x4, y4, mask_parallelogram, 0) 701 | # Print end points 702 | print([x1, y1, x2, y2, x3, y3, x4, y4]) 703 | 704 | 705 | valid_parallelogram_list = [] 706 | points_on_parallelogram = [] 707 | print("Length of para_gram_options:") 708 | print(len(para_gram_options)) 709 | for line in para_gram_options: 710 | points_on_line = valid_parallelogram(line) 711 | points_on_parallelogram.append(points_on_line) 712 | if points_on_line > 0: 713 | #draw_parallelogram( line ) 714 | add_parallelogram_mask(line) 715 | # plt.imshow(edge_map, cmap='gray') 716 | # plt.show() 717 | # plt.close() 718 | 719 | # valid_parallelogram_list.append( line ) 720 | 721 | # print("Points_on_parallelogram = ") 722 | # print( points_on_parallelogram ) 723 | 724 | # Use the mask 725 | masked_image_list = [] 726 | for i in range(0, row): 727 | for j in range(0, col): 728 | # Use the mask 729 | masked_image_list.append(grayImage[i][j] * mask_parallelogram[i][j]) 730 | # masked_image_list.append( testImage[ i*col + j*3 + 0 ] * mask_parallelogram[i][j] ) 731 | # masked_image_list.append( testImage[ i*col + j*3 + 1 ] * mask_parallelogram[i][j] ) 732 | # masked_image_list.append( testImage[ i*col + j*3 + 2 ] * mask_parallelogram[i][j] ) 733 | 734 | 735 | maskedImage = np.array(masked_image_list).reshape([row, col]) 736 | 737 | # plt.imshow(maskedImage, cmap = "gray") 738 | # #plt.show() 739 | # plt.savefig("maskedImage.png") 740 | # plt.close() 741 | matplotlib.image.imsave('maskedImage.png', maskedImage) 742 | 743 | 744 | # Saving filtered image to new file 745 | --------------------------------------------------------------------------------