├── simulationSystem └── none.txt ├── data ├── imgs │ ├── 0001.jpg │ └── 0002.jpg ├── segs │ ├── 0001segre.jpg │ ├── 0001segre.npz │ ├── 0002segre.jpg │ └── 0002segre.npz ├── vpts │ ├── 0001vptpre.npz │ └── 0002vptpre.npz └── lines │ ├── 0001nlines.npz │ └── 0002nlines.npz ├── misc ├── figs │ ├── 0001.png │ └── 0002.png └── vps_models │ ├── download_link.txt │ ├── config.yaml │ └── vpt_transform.py ├── requirements.txt ├── config └── estimation_config.ini ├── lineDrawingConfig.py ├── filesIO.py ├── demo.py ├── README.md ├── lineRefinement.py ├── lineClassification.py └── heightMeasurement.py /simulationSystem/none.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/imgs/0001.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yzre/SIHE/HEAD/data/imgs/0001.jpg -------------------------------------------------------------------------------- /data/imgs/0002.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yzre/SIHE/HEAD/data/imgs/0002.jpg -------------------------------------------------------------------------------- /misc/figs/0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yzre/SIHE/HEAD/misc/figs/0001.png -------------------------------------------------------------------------------- /misc/figs/0002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yzre/SIHE/HEAD/misc/figs/0002.png -------------------------------------------------------------------------------- /data/segs/0001segre.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yzre/SIHE/HEAD/data/segs/0001segre.jpg -------------------------------------------------------------------------------- /data/segs/0001segre.npz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yzre/SIHE/HEAD/data/segs/0001segre.npz -------------------------------------------------------------------------------- /data/segs/0002segre.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yzre/SIHE/HEAD/data/segs/0002segre.jpg -------------------------------------------------------------------------------- /data/segs/0002segre.npz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yzre/SIHE/HEAD/data/segs/0002segre.npz -------------------------------------------------------------------------------- /data/vpts/0001vptpre.npz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yzre/SIHE/HEAD/data/vpts/0001vptpre.npz -------------------------------------------------------------------------------- /data/vpts/0002vptpre.npz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yzre/SIHE/HEAD/data/vpts/0002vptpre.npz -------------------------------------------------------------------------------- /data/lines/0001nlines.npz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yzre/SIHE/HEAD/data/lines/0001nlines.npz -------------------------------------------------------------------------------- /data/lines/0002nlines.npz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yzre/SIHE/HEAD/data/lines/0002nlines.npz -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib==3.3.1 2 | numpy==1.19.1 3 | opencv_python==4.4.0.42 4 | scikit_image==0.18.1 5 | scikit_learn==1.1.1 6 | scipy==1.5.2 7 | skimage==0.0 8 | 9 | -------------------------------------------------------------------------------- /misc/vps_models/download_link.txt: -------------------------------------------------------------------------------- 1 | links 2 | 3 | 1. google drive link 4 | 5 | https://drive.google.com/file/d/1PQCgmbIUBq1qrhzJJiRIJ8q9kcYwniu_/view?usp=sharing 6 | 7 | 8 | 2. 百度网盘链接 9 | 10 | 链接:https://pan.baidu.com/s/1wqG0kUccP3zUcdIArP5w5g 11 | 提取码:6126 12 | 13 | 14 | -------------------------------------------------------------------------------- /config/estimation_config.ini: -------------------------------------------------------------------------------- 1 | [LINE_CLASSIFY] 2 | LineScore = 0.94 3 | AngleThres = 10 4 | 5 | [HEIGHT_MEAS] 6 | InitialDistThres = 0.3 7 | DistThres = 100.0 8 | ValidHeightRatio = 0.001 9 | MinValidHeightNum = 1 10 | NSigmaCriterion = 0 11 | MaxDBSANDist = 50 12 | 13 | [LINE_REFINE] 14 | Edge_Thres = 4 15 | 16 | [SEGMENTATION] 17 | SkyLabel = 2 18 | BuildingLabel = 1 19 | GroundLabel = 6, 11 20 | 21 | [STREET_VIEW] 22 | CameraHeight = 2.5 23 | HVFoV = 90 24 | 25 | [GROUND_TRUTH] 26 | Exist = 0 -------------------------------------------------------------------------------- /lineDrawingConfig.py: -------------------------------------------------------------------------------- 1 | # -*- encoding:utf-8 -*- 2 | 3 | import matplotlib.pyplot as plt 4 | import matplotlib as mpl 5 | 6 | PLTOPTS = {"color": "#33FFFF", "s": 15, "edgecolors": "none", "zorder": 5} 7 | cmap = plt.get_cmap("jet") 8 | norm = mpl.colors.Normalize(vmin=0.9, vmax=1.0) 9 | sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm) 10 | sm.set_array([]) 11 | colors_tables = ['blue','orange','green','red','purple','brown','pink', 'gray', 'olive','cyan'] 12 | def c(x): 13 | return sm.to_rgba(x) 14 | 15 | 16 | -------------------------------------------------------------------------------- /misc/vps_models/config.yaml: -------------------------------------------------------------------------------- 1 | io: 2 | augmentation_level: 2 3 | datadir: data/gsv/ 4 | dataset: Wireframe 5 | focal_length: 1.0 6 | logdir: logs/ 7 | num_vpts: 3 8 | num_workers: 6 9 | resume_from: 10 | tensorboard_port: 0 11 | validation_debug: 120 12 | validation_interval: 12000 13 | model: 14 | backbone: stacked_hourglass 15 | batch_size: 12 16 | conic_6x: false 17 | depth: 4 18 | fc_channel: 1024 19 | im2col_step: 11 20 | multires: 21 | - 0.0013457768043554 22 | - 0.0051941870036646 23 | - 0.02004838034795 24 | - 0.0774278195486317 25 | - 0.299564810864565 26 | num_blocks: 1 27 | num_stacks: 1 28 | output_stride: 4 29 | smp_multiplier: 2 30 | smp_neg: 1 31 | smp_pos: 1 32 | smp_rnd: 3 33 | upsample_scale: 1 34 | optim: 35 | amsgrad: true 36 | lr: 0.0001 37 | lr_decay_epoch: 5 38 | max_epoch: 36 39 | name: Adam 40 | weight_decay: 1.0e-05 41 | -------------------------------------------------------------------------------- /filesIO.py: -------------------------------------------------------------------------------- 1 | # -*-encoding:utf-8 -*- 2 | 3 | import numpy as np 4 | import skimage 5 | import matplotlib.pyplot as plt 6 | 7 | def load_vps_2d(filename): 8 | """ 9 | load vanishing points 10 | :param filename: 11 | :return: 12 | """ 13 | with np.load(filename) as npz: 14 | vpts_pd_2d = npz['vpts_re'] 15 | return vpts_pd_2d 16 | 17 | 18 | def load_line_array(filename): 19 | """ 20 | load lines as well as scores. 21 | :param filename: 22 | :return: 23 | """ 24 | with np.load(filename) as npz: 25 | nlines = npz["nlines"] 26 | nscores = npz["nscores"] 27 | 28 | return nlines, nscores 29 | 30 | 31 | def load_seg_array(filename): 32 | """ 33 | load segmentation results, the values represent different labels. 34 | :param filename: 35 | :return: 36 | """ 37 | with np.load(filename) as npz: 38 | seg_array = npz["seg"] 39 | return seg_array 40 | 41 | 42 | def load_zgts(filename): 43 | """ 44 | load the ground truth image of z values, if exists. 45 | :param filename: 46 | :return: 47 | """ 48 | with np.load(filename) as npz: 49 | zgt = npz["height"] 50 | return zgt -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | # -*-encoding:utf-8-*- 2 | 3 | import math 4 | import os 5 | import sys 6 | import numpy as np 7 | import configparser 8 | from heightMeasurement import heightCalc 9 | 10 | if __name__ == '__main__': 11 | if len(sys.argv) != 3: 12 | print("usage: python %s img_path config_fname" % sys.argv[0]) 13 | exit(-1) 14 | 15 | img_path = sys.argv[1] # the path of the image folder 16 | cfg_fname = sys.argv[2] # the path of the config file 17 | 18 | # load configurations 19 | config = configparser.ConfigParser() 20 | config.read(cfg_fname) 21 | 22 | # initialize the intrinsic parameters 23 | cx = cy =320 # the position of the image center 24 | hvfov = float(config["STREET_VIEW"]["HVFoV"]) # the field of view 25 | fx = math.tan(np.deg2rad(hvfov/2.0)) * cx # focal length 26 | fy = math.tan(np.deg2rad(hvfov/2.0)) * cy # focal length 27 | intrins = np.asarray([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]) # intrinsic matrix 28 | 29 | # loop over the data 30 | for root, dir, files in os.walk(img_path): 31 | for file in files: 32 | if '.jpg' in file: 33 | img_fname = os.path.join(root, file) 34 | 35 | # the file name of the vanishing points 36 | vpt_fname = img_fname.replace('/imgs/', '/vpts/') 37 | vpt_fname = vpt_fname.replace('.jpg', 'vptpre.npz') 38 | if not os.path.exists(vpt_fname): 39 | vpt_fname = 'none' 40 | 41 | # the file name of the detected line segments 42 | line_fname = img_fname.replace('/imgs/', '/lines/') 43 | line_fname = line_fname.replace('.jpg', 'nlines.npz') 44 | 45 | # the file name of the semantic segmentation data 46 | seg_fname = img_fname.replace('/imgs/', '/segs/') 47 | seg_fname = seg_fname.replace('.jpg', 'segre.npz') 48 | 49 | # the file name of ground truth (default: none) 50 | zgt_fname = 'none' 51 | if int(config["GROUND_TRUTH"]["Exist"]): 52 | zgt_fname = vpt_fname.replace('/imgs/', '/zgts/') 53 | zgt_fname = zgt_fname.replace('.jpg', 'height.npz') 54 | 55 | fname_dict = dict() 56 | fname_dict["vpt"] = vpt_fname 57 | fname_dict["img"] = img_fname 58 | fname_dict["line"] = line_fname 59 | fname_dict["seg"] = seg_fname 60 | fname_dict["zgt"] = zgt_fname 61 | 62 | # estimation of building height 63 | heightCalc(fname_dict, intrins, config, img_size=[640, 640], pitch=25, use_pitch_only=0, use_detected_vpt_only=0, verbose=True) 64 | 65 | # the end 66 | print('end') 67 | -------------------------------------------------------------------------------- /misc/vps_models/vpt_transform.py: -------------------------------------------------------------------------------- 1 | # by Yizhen Yan 2 | 3 | import numpy as np 4 | 5 | 6 | # ######################### to transform vpts 7 | def to_pixel_new(v, focal_length): 8 | x = v[0] / v[2] * focal_length * 256 + 256 # 256 is half the image width 9 | y = -v[1] / v[2] * focal_length * 256 + 256 # 256 is half the image width 10 | return x, y 11 | 12 | 13 | def order_vpt(vps_2D, w=640.0): 14 | # order the vps_2D again to make v3 the vertical vps, and v1 & v2 the horizontal vps 15 | # here vps_2D_ordered = vps_2D cannot be used to initialize the vps_2D_ordered 16 | # because if so, the vps_2D_ordered will change when vps_2D change 17 | # w is image width, h is image height 18 | 19 | # in neurvps, it only deals with images whose h is equal to w 20 | # w = 640.0 # image width 21 | h = w # image height 22 | vps_2D_ordered = np.array([[0.0, 0.0], [0.0, 0.0], [0.0, 0.0]]) 23 | dy = abs(vps_2D[:, 1] - h/2) # the distance from the principle point in the y direction 24 | # dy_max_id = np.where(dy == np.max(dy)) # the indexes of the max dy 25 | dy_max_id = np.where(np.max(dy) - dy < 1) # the indexes of the vps that have dys very close to max dy 26 | dy_max_id = dy_max_id[0] # to get the indexes array from the dy_max_id tuple 27 | if dy_max_id.size == 1: 28 | v3 = vps_2D[dy_max_id[0], :] 29 | v3_id = dy_max_id[0] 30 | else: 31 | dx1 = abs(vps_2D[dy_max_id[0], 0] - w/2) # the distance from the principle point in the x direction 32 | dx2 = abs(vps_2D[dy_max_id[1], 0] - w/2) # the distance from the principle point in the x direction 33 | if dx1 < dx2: 34 | v3 = vps_2D[dy_max_id[0], :] # the vertical vps 35 | v3_id = dy_max_id[0] 36 | else: 37 | v3 = vps_2D[dy_max_id[1], :] 38 | v3_id = dy_max_id[1] 39 | 40 | v_order = np.array([0, 1, 2]) 41 | vh_id = np.where(v_order != v3_id) # the indexes of the horizontal vps 42 | vh_id = vh_id[0] # to get the indexes array from the dy_max_id tuple 43 | # if the x of one vps larger than the other one, it is the right horizontal vps 44 | if vps_2D[vh_id[0], 0] > vps_2D[vh_id[1], 0]: 45 | v1 = vps_2D[vh_id[0], :] # the right horizontal vps 46 | v2 = vps_2D[vh_id[1], :] # the left horizontal vps 47 | else: 48 | v1 = vps_2D[vh_id[1], :] # the right horizontal vps 49 | v2 = vps_2D[vh_id[0], :] # the left horizontal vps 50 | 51 | # here vps_2D[i,:]=vi cannot be used because if vps_2D changed, the vi calculated above 52 | # will change as well, so another variable vps_2D_ordered is used to avoid the problem 53 | vps_2D_ordered[0, :] = v1 54 | vps_2D_ordered[1, :] = v2 55 | vps_2D_ordered[2, :] = v3 56 | 57 | # print('vps_2D ordered: ') 58 | # print(vps_2D_ordered) 59 | return vps_2D_ordered 60 | 61 | 62 | def transform_vpt(vpts, fov=120.0, orgimg_width=640.0): 63 | # transform 3D vpts to 2D vpts with image coordinates of original resolution 64 | # when detecting the vpts, the images are resized to 512*512 65 | # fov is field of view in degrees and used to compute focal length 66 | # orgimg_width is the width (resolution) of the original image 67 | 68 | v1 = vpts[0] 69 | v2 = vpts[1] 70 | v3 = vpts[2] 71 | 72 | # fov = 120.0 # field of view in degree 73 | f = 1 / np.tan(np.deg2rad(fov/2)) 74 | print('focal length') 75 | print(f) 76 | 77 | p1 = to_pixel_new(v1, f) 78 | p2 = to_pixel_new(v2, f) 79 | p3 = to_pixel_new(v3, f) 80 | vpts_2d = np.array([p1, p2, p3]) 81 | print('2d vpts') 82 | print(vpts_2d) 83 | 84 | # orgimg_width = 640.0 # the width (resolution) of the original image 85 | p1t = np.multiply(p1, orgimg_width / 512) 86 | p2t = np.multiply(p2, orgimg_width / 512) 87 | p3t = np.multiply(p3, orgimg_width / 512) 88 | vpts_2d_t = np.array([p1t, p2t, p3t]) 89 | print('transformed 2d vpts') 90 | print(vpts_2d_t) 91 | 92 | vpts_2d_ordered = order_vpt(vpts_2d_t, w=orgimg_width) 93 | print('ordered 2d vpts') 94 | print(vpts_2d_ordered) 95 | 96 | return vpts_2d_ordered 97 | # ######################### to transform vpts 98 | 99 | 100 | if __name__ == "__main__": 101 | 102 | # vpts_pd: the direct vpt output from the model 103 | vpts_pd = np.load('vpt_filename') # load from files or input the model output 104 | # show vpts detected 105 | print("/n the vpts of image is: ") 106 | print(vpts_pd) 107 | 108 | # transform vpts_pd to 2D pixel coordinates and transform them to coordinates of 640*640 image size 109 | vpts_re = transform_vpt(vpts_pd, fov=90.0, orgimg_width=512.0) # for gsv images with size 512x512 and fov 90 110 | 111 | # for saving the predictions 112 | save_name = './vpts_results_sample.npz' 113 | np.savez( 114 | save_name, 115 | vpts_pd=vpts_pd, 116 | vpts_re=vpts_re, 117 | ) 118 | 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SIHE: estimation of building height from a single street view image 2 | This repository contains the Python implementation of the building height estimation method 3 | in the paper: *Yan Y, Huang B. ["Estimation of building height using a single street view image via deep neural networks"](https://www.sciencedirect.com/science/article/abs/pii/S0924271622002106)*. ISPRS Journal of Photogrammetry and Remote Sensing, 2022, 192: 83-98. 4 | 5 | # Introduction 6 | SIHE (Single Image Height Estimation) is a tool designed to estimate building heights from a single street view image, 7 | utilizing single-view metrology principles. By harnessing geometric information and features like vanishing points, 8 | line segments, and semantic masks, which can be automatically extracted through deep neural networks, SIHE can 9 | determines building vertical lines and building heights. 10 | 11 | # Setup 12 | Simply clone this repo or download the zip file onto your local machine, then install `requirements.txt` file to install relevant python packages: 13 | 14 | ``` 15 | $ git clone https://github.com/ 16 | $ python install -r requirements.txt 17 | ``` 18 | 19 | # Code Structure 20 | Below is a quick overview of the function of each file. 21 | 22 | ```bash 23 | ########################### height estimation code ########################### 24 | config/ # configurations 25 | estimation_config.ini # default parameters for height estimation 26 | data/ # default folder for placing the data 27 | imgs/ # folder for original street view images 28 | lines/ # folder for detected line segment files 29 | segs/ # folder for semantic segmented image files 30 | vpts/ # folder for detected vanishing point files 31 | misc/ # misc files 32 | simulationSystem/ # scripts for simulation and theoretical analysis 33 | 34 | demo.py # main function 35 | filesIO.py # functions for loading files 36 | heightMeasurement.py # functions for height measurement 37 | lineClassification.py # functions for line segment classification 38 | lineDrawingConfig.py # script for line visualization configuration 39 | lineRefinement.py # functions for line segment refinement 40 | ``` 41 | 42 | # Get started 43 | Use `demo.py` to run the code with sample data and default parameters. Execute the following command in the terminal, 44 | or add `img_path config_fname` in the Parameters when run `demo.py` in PyCharm: 45 | ```bash 46 | python ./demo.py ./data/imgs/ ./config/estimation_config.ini 47 | ``` 48 | 49 | The height estimation results will be written to `./data/ht_results/` accordingly. 50 | 51 | The main estimation function is the `heightCalc()` function and 52 | there are two important parameters: 53 | 54 | * `use_pitch_only`: when the value is '1', use only the pitch angle to calculate 55 | vanishing line and vertical vanishing point for height measurement 56 | * `use_detected_vpt_only`: when the value is '1', use only the detected vanishing 57 | points for height measurement 58 | 59 | The vanishing points can be detected from the image using neural networks, 60 | or calculated using pitch angle (when the rotation angles of the image are known 61 | from the data source, e.g. Google Street View). The setting of the two parameters 62 | depends on how vanishing points are obtained. 63 | 64 | # How to use 65 | In the estimation of building height, semantic segmentation map of the street view 66 | image, line segments, and vanishing points are used. Since they can be obtained 67 | through different neural networks, the codes of the networks are not integrated 68 | in the main stream of height estimation. Instead, the street view images are 69 | separately processed by three networks, and the result files are prepared for later 70 | height measurement (as in `./data/lines`, `./data/segs`, `./data/vpts`). 71 | 72 | * line segment detection: use LCNN network [1] 73 | * semantic segmentation: use MMSegmentation toolbox [2] 74 | * vanishing point detection: use NeurVPS network [3] with newly-trained model 75 | (in `./misc/vps_models/`) 76 | 77 | The installation and usage of the networks can be referred to their official 78 | repository. More details about the setup for this study can be referred to our paper. 79 | In addition, other networks can be tried for better results. If so, the config file `estimation_config.ini` 80 | may need to be modified to align with the data, such as the segmentation labels. 81 | 82 | When the above mentioned result files are prepared, the `demo.py` can be used to estimate heights. 83 | 84 | 85 | # Example results 86 | 87 | | Fig. 1 | Fig. 2 | 88 | | ---------------------------------- | ---------------------------------- | 89 | | ![fig.1](./misc/figs/0001.png) | ![fig.2](./misc/figs/0002.png) | 90 | 91 | 92 | 93 | # Acknowledgements 94 | We appreciate the open source of the following projects: 95 | 96 | [1] [lcnn](https://github.com/zhou13/lcnn) \ 97 | [2] [mmsegmentation](https://github.com/open-mmlab/mmsegmentation) \ 98 | [3] [neurvps](https://github.com/zhou13/neurvps) 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /lineRefinement.py: -------------------------------------------------------------------------------- 1 | # -*-encoding:utf-8-*- 2 | 3 | import matplotlib.pyplot as plt 4 | from lineDrawingConfig import * 5 | import skimage 6 | import numpy as np 7 | 8 | 9 | def extendLines(pt1, pt2, segmt, config): 10 | """ 11 | Extend the vertical line segments by referring to the segmentation image 12 | :param pt1: end point of the line 13 | :param pt2: end point of the line 14 | :param segmt: semantic segmentation image array 15 | :return: 16 | """ 17 | 18 | sky_label = int(config["SEGMENTATION"]["SkyLabel"]) 19 | building_label = int(config["SEGMENTATION"]["BuildingLabel"]) 20 | ground_label = np.cast["int"](config["SEGMENTATION"]["GroundLabel"].split(',')) 21 | edge_thres = np.cast["int"](config["LINE_REFINE"]["Edge_Thres"].split(',')) 22 | 23 | if pt1[0] > pt2[0]: # 0 is the x axis 24 | pt_up = pt2 25 | pt_down = pt1 26 | else: 27 | pt_up = pt1 28 | pt_down = pt2 29 | 30 | if np.linalg.norm(pt_down - pt_up) == 0: 31 | return [], [] 32 | direction = (pt_down - pt_up) / np.linalg.norm(pt_down - pt_up) 33 | pt_up_end = pt_up 34 | pt_down_end = pt_down 35 | pt_middle = (pt_up + pt_down) / 2.0 36 | 37 | rows, cols = segmt.shape 38 | if pt_up_end[0] > rows - 2: 39 | pt_up_end[0] = rows - 2 40 | if pt_up_end[1] > cols - 2: 41 | pt_up_end[1] = cols - 2 42 | if pt_down_end[0] > rows - 2: 43 | pt_down_end[0] = rows - 2 44 | if pt_down_end[1] > cols - 2: 45 | pt_down_end[1] = cols - 2 46 | 47 | if pt_middle[0] >= rows - 1 or pt_middle[1] >= cols - 1: 48 | return [], [] 49 | 50 | if segmt[np.cast['int'](pt_up_end[0] + 0.5)][np.cast['int'](pt_up_end[1] + 0.5)] != building_label or \ 51 | segmt[np.cast['int'](pt_down_end[0] + 0.5)][np.cast['int'](pt_down_end[1] + 0.5)] != building_label or \ 52 | segmt[np.cast['int'](pt_middle[0] + 0.5)][np.cast['int'](pt_middle[1] + 0.5)] != building_label: 53 | return [], [] 54 | 55 | flag = 1 56 | while flag: 57 | pt_up_end = pt_up_end - direction 58 | if pt_up_end[0] < 0 or pt_up_end[1] < 0 or pt_up_end[1] >= rows - 1: 59 | flag = 0 60 | pt_up_end = pt_up_end + direction 61 | continue 62 | if segmt[np.cast['int'](pt_up_end[0] + 0.5)][np.cast['int'](pt_up_end[1] + 0.5)] == sky_label: 63 | flag = 0 64 | pt_up_end = pt_up_end + direction 65 | 66 | flag = 1 67 | out_of_building = False 68 | while flag: 69 | pt_down_end = pt_down_end + direction 70 | if pt_down_end[0] >= cols - 1 or pt_down_end[1] < 0 or pt_down_end[1] >= rows - 1: 71 | flag = 0 72 | continue 73 | if segmt[np.cast['int'](pt_down_end[0] + 0.5)][np.cast['int'](pt_down_end[1] + 0.5)] != building_label and \ 74 | not segmt[np.cast['int'](pt_down_end[0] + 0.5)][np.cast['int'](pt_down_end[1] + 0.5)] in ground_label: 75 | out_of_building = True 76 | else: 77 | if segmt[np.cast['int'](pt_down_end[0] + 0.5)][np.cast['int'](pt_down_end[1] + 0.5)] == building_label: 78 | out_of_building = False 79 | if segmt[np.cast['int'](pt_down_end[0] + 0.5)][np.cast['int'](pt_down_end[1] + 0.5)] in ground_label and \ 80 | not out_of_building: 81 | flag = 0 82 | pt_down_end = pt_down_end - direction 83 | else: # reach the ground and the previous label is not building 84 | if segmt[np.cast['int'](pt_down_end[0] + 0.5)][np.cast['int'](pt_down_end[1] + 0.5)] in ground_label: 85 | return [], [] 86 | pass 87 | if pt_up_end[0] > cols - 1 - edge_thres or \ 88 | pt_up_end[1] < edge_thres or pt_up_end[1] > rows - edge_thres or \ 89 | pt_down_end[0] < edge_thres or pt_down_end[0] > cols - 1 - edge_thres or \ 90 | pt_down_end[1] < edge_thres or pt_down_end[1] > rows - 1 - edge_thres: 91 | return [],[] 92 | return pt_up_end, pt_down_end 93 | 94 | 95 | def verticalLineExtending(img_name, vertical_lines, segimg, vptz, config, verbose=True): 96 | """ 97 | Extend the vertical line segments to make their end points on the bottom and the roof of the buildings. 98 | :param img_name: the file name of the image 99 | :param vertical_lines: vertical line segments 100 | :param segimg: semantic segmentation image array 101 | :param vptz: vertical vanishing point 102 | :param config: configuration 103 | :param verbose: when true, show the results 104 | :return: 105 | """ 106 | if verbose: 107 | plt.close() 108 | org_img = skimage.io.imread(img_name) 109 | plt.imshow(org_img) 110 | 111 | extd_lines = [] 112 | for line in vertical_lines: 113 | # when reach the part of sky, stop extending 114 | # when go down until reach the ground 115 | line = lineRefinementWithVPT(line, vptz) 116 | a = line[0] 117 | b = line[1] 118 | extd_a, extd_b = extendLines(a, b, segimg, config) 119 | if len(extd_a) == 0 or len(extd_b) == 0: 120 | continue 121 | extd_lines.append([extd_a, extd_b]) 122 | 123 | if verbose: 124 | plt.plot([extd_a[1], extd_b[1]], [extd_a[0], extd_b[0]], c='y', linewidth=2) 125 | plt.scatter(extd_a[1], extd_a[0], **PLTOPTS) 126 | plt.scatter(extd_b[1], extd_b[0], **PLTOPTS) 127 | 128 | if verbose: 129 | # plt.show() 130 | plt.close() 131 | 132 | return extd_lines 133 | 134 | 135 | def verticalLineExtendingWithBRLines(img_name, vertical_lines, roof_lines, bottom_lines, segimg, config, verbose=True): 136 | """ 137 | Extend the vertical line segments using the classified roof and bottom lines 138 | :param img_name: the file name of the image 139 | :param vertical_lines: vertical line segments 140 | :param roof_lines: roof line segments 141 | :param bottom_lines: bottom line segments 142 | :param segimg: semantic segmentation image array 143 | :param config: configuration 144 | :param verbose: when true, show the results 145 | :return: 146 | """ 147 | 148 | if verbose: 149 | org_img = skimage.io.imread(img_name) 150 | plt.close() 151 | plt.imshow(org_img) 152 | 153 | rows, cols = segimg.shape 154 | extd_lines = [] 155 | for vl in vertical_lines: 156 | pt_rl = [] 157 | for rl in roof_lines: 158 | # roof lines 159 | vl_direction = vl[0] - vl[1] 160 | rl_direction = rl[0] - rl[1] 161 | A = np.transpose(np.vstack([vl_direction, -rl_direction])) 162 | b = np.transpose(rl[0] - vl[0]) 163 | x = np.matmul(np.linalg.inv(A), b) 164 | pt = vl_direction*x[0] + vl[0] 165 | 166 | pt = np.cast['int'](pt + 0.5) 167 | 168 | if x[0] > 2 or x[0] < -2: 169 | continue 170 | 171 | if pt[0] < 10 or pt[0] > rows - 10 or pt[1] < 10 or pt[1] > cols - 10: 172 | continue 173 | 174 | # print(segimg[pt[0] - 5: pt[0] + 5, pt[1] - 5:pt[1] + 5]) 175 | if np.std(segimg[pt[0] - 10: pt[0] + 10, pt[1] - 10:pt[1] + 10]) == 0: 176 | continue 177 | 178 | if len(pt_rl) == 0: 179 | pt_rl = pt 180 | else: 181 | if pt_rl[0] > pt[0]: 182 | pt_rl = pt 183 | 184 | if len(pt_rl) == 0: 185 | # vl[0] = pt_rl 186 | # else: 187 | continue 188 | 189 | pt_bl = [] 190 | for bl in bottom_lines: 191 | # bottom lines 192 | vl_direction = vl[0] - vl[1] 193 | bl_direction = bl[0] - bl[1] 194 | A = np.transpose(np.vstack([vl_direction, -bl_direction])) 195 | b = np.transpose(bl[0] - vl[0]) 196 | x = np.matmul(np.linalg.inv(A), b) 197 | pt = vl_direction*x[0] + vl[0] 198 | 199 | pt = np.cast['int'](pt + 0.5) 200 | 201 | if x[0] > 2 or x[0] < -2: 202 | continue 203 | 204 | if pt[0] < 10 or pt[0] > rows - 10 or pt[1] < 10 or pt[1] > cols - 10: 205 | continue 206 | 207 | if np.std(segimg[pt[0] - 10: pt[0] + 10, pt[1] - 10:pt[1] + 10]) == 0: 208 | continue 209 | 210 | if len(pt_bl) == 0: 211 | pt_bl = pt 212 | else: 213 | if pt_bl[0] < pt[0]: 214 | pt_bl = pt 215 | 216 | if len(pt_bl) == 0: 217 | # vl[1] = pt_bl 218 | # else: 219 | continue 220 | 221 | # if verbose: 222 | # plt.figure() 223 | # plt.imshow(org_img) 224 | # plt.plot([vl[0][1], vl[1][1]], [vl[0][0], vl[1][0]], c='r', linewidth=2) 225 | # # plt.plot([rl[0][1], rl[1][1]], [rl[0][0], rl[1][0]], c='r', linewidth=2) 226 | # plt.scatter(pt_rl[1], pt_rl[0], **PLTOPTS) 227 | # plt.scatter(pt_bl[1], pt_bl[0], **PLTOPTS) 228 | # plt.show() 229 | 230 | extd_lines.append([pt_rl, pt_bl]) 231 | 232 | return extd_lines 233 | 234 | 235 | def pointOnLine(a, b, p): 236 | """ 237 | Project a point (p) onto the line (a-b) 238 | :param a: end point of the line 239 | :param b: end point of the line 240 | :param p: a point 241 | :return: 242 | """ 243 | # ap = p - a 244 | # ab = b - a 245 | # result = a + np.dot(ap, ab) / np.dot(ab, ab) * ab 246 | # return result 247 | l2 = np.sum((a - b) ** 2) 248 | if l2 == 0: 249 | print('p1 and p2 are the same points') 250 | # The line extending the segment is parameterized as p1 + t (p2 - p1). 251 | # The projection falls where t = [(p3-p1) . (p2-p1)] / |p2-p1|^2 252 | # if you need the point to project on line extention connecting p1 and p2 253 | t = np.sum((p - a) * (b - a)) / l2 254 | # if you need to ignore if p3 does not project onto line segment 255 | # if t > 1 or t < 0: 256 | # print('p3 does not project onto p1-p2 line segment') 257 | # 258 | # # if you need the point to project on line segment between p1 and p2 or closest point of the line segment 259 | # t = max(0, min(1, np.sum((p3 - p1) * (p2 - p1)) / l2)) 260 | projection = a + t * (b - a) 261 | return projection 262 | 263 | 264 | def lineRefinementWithVPT(line, vpt): 265 | """ 266 | Use vanishing point to refine line segments. Slightly rotate the line around its middle point to make its direction 267 | the same as the one from its middle point to the vanishing point. The projected points of the original end points 268 | of the line onto the new direction will be the new end points of the refined line. 269 | :param line: line segment with two end points 270 | :param vpt: vanishing point 271 | :return: 272 | """ 273 | a = line[0] 274 | b = line[1] 275 | mpt = (a + b) / 2.0 276 | line[0] = pointOnLine(vpt, mpt, a) 277 | line[1] = pointOnLine(vpt, mpt, b) 278 | 279 | return line 280 | -------------------------------------------------------------------------------- /lineClassification.py: -------------------------------------------------------------------------------- 1 | # -*-encoding:utf-8-*- 2 | 3 | import copy 4 | import numpy as np 5 | import itertools 6 | import matplotlib.pyplot as plt 7 | import skimage 8 | import os 9 | from collections import Counter 10 | from lineDrawingConfig import * 11 | from lineRefinement import * 12 | from sklearn.cluster import DBSCAN 13 | 14 | 15 | def classifyWithVPTs(n1, n2, vpt, config): 16 | """ 17 | Use the vanishing point to classify the line segments. 18 | :param n1: end point of the line segment 19 | :param n2: end point of the line segment 20 | :param vpt: the vanishing point 21 | :param config: configuration 22 | :return: 23 | """ 24 | 25 | flag = False 26 | t_angle = float(config["LINE_CLASSIFY"]["AngleThres"]) # the threshold of the anlge error (degree) 27 | 28 | # for points n1 and n2, the elements are arranged as [row, column]=[y, x], but for vpt, it's [column, row]=[x, y] 29 | # therefore, the following line is needed 30 | p1 = np.array([n1[1], n1[0]]) 31 | p2 = np.array([n2[1], n2[0]]) 32 | 33 | # # compare the direction of the line with the directions of the two lines p1-vpt, p2-vpt 34 | # d1 = p2 - p1 35 | # d2 = vpt - p1 36 | # angle1 = np.rad2deg(np.arccos(np.dot(d1, d2)/(np.linalg.norm(d1)*np.linalg.norm(d2)))) 37 | # d3 = p1 - p2 38 | # d4 = vpt - p2 39 | # angle2 = np.rad2deg(np.arccos(np.dot(d3, d4)/(np.linalg.norm(d3)*np.linalg.norm(d4)))) 40 | # if (angle1 < t_angle or 180 - angle1 < t_angle) or (angle2 < t_angle or 180 - angle2 < t_angle): 41 | # is_vertical = 1 42 | 43 | # compare the direction of the line with the direction of the middle point of the line to vpt 44 | mpt = [(p1[0] + p2[0]) / 2.0, (p1[1] + p2[1]) / 2.0] # the middle point of the line 45 | d1 = p2 - p1 46 | d2 = vpt - mpt 47 | angle = np.rad2deg(np.arccos(np.dot(d1, d2) / (np.linalg.norm(d1) * np.linalg.norm(d2)))) 48 | if angle < t_angle or 180 - angle < t_angle: 49 | flag = True 50 | 51 | return flag 52 | 53 | 54 | def check_if_line_lies_in_building_area(seg_img, a, b, config)->bool: 55 | """ 56 | check if the line segment lies in building area 57 | :param seg_img: the semantic segmentation image array 58 | :param a: end point of the line segment 59 | :param b: end point of the line segment 60 | :param config: configuration 61 | :return: 62 | """ 63 | 64 | middle = (a + b)/2.0 # middle point of the line segment 65 | norm_direction = (a - b) / np.linalg.norm(a - b) 66 | ppd_dir = np.asarray([norm_direction[1], -norm_direction[0]]) 67 | 68 | sky_label = int(config["SEGMENTATION"]["SkyLabel"]) 69 | building_label = int(config["SEGMENTATION"]["BuildingLabel"]) 70 | ground_label = np.cast["int"](config["SEGMENTATION"]["GroundLabel"].split(',')) 71 | 72 | ratio = 10 73 | ppd_dir = ratio * ppd_dir 74 | point_check_list = copy.deepcopy(a) 75 | point_check_list = np.vstack([point_check_list, a - ppd_dir]) 76 | point_check_list = np.vstack([point_check_list, a + ppd_dir]) 77 | point_check_list = np.vstack([point_check_list, b]) 78 | point_check_list = np.vstack([point_check_list, b - ppd_dir]) 79 | point_check_list = np.vstack([point_check_list, b + ppd_dir]) 80 | point_check_list = np.vstack([point_check_list, middle]) 81 | point_check_list = np.vstack([point_check_list, middle - ppd_dir]) 82 | point_check_list = np.vstack([point_check_list, middle + ppd_dir]) 83 | 84 | total_num = 0 85 | local_num = 0 86 | rows, cols = seg_img.shape 87 | flag = True 88 | for pcl in point_check_list: 89 | total_num = total_num + 1 90 | # swap the x,y coordinate 91 | y_int = np.cast["int"](pcl[0] + 0.5) 92 | x_int = np.cast["int"](pcl[1] + 0.5) 93 | if x_int < 0 or x_int > cols - 1 or y_int < 0 or y_int > rows - 1: 94 | local_num = local_num + 1 95 | continue 96 | if seg_img[y_int, x_int] == building_label: 97 | local_num = local_num + 1 98 | if np.remainder(total_num, 3) == 0 and local_num == 0: 99 | flag = False 100 | break 101 | else: 102 | if np.remainder(total_num, 3) == 0: 103 | local_num = 0 104 | return flag 105 | 106 | 107 | def check_if_bottom_lines(seg_img, a, b, config)->bool: 108 | """ 109 | Check whether a line is on the bottom of a building. 110 | :param seg_img: semantic segmentation image array 111 | :param a: end point of the line 112 | :param b: end point of the line 113 | :param config: configuration 114 | :return: 115 | """ 116 | 117 | middle = (a + b)/2.0 # middle point of the line 118 | norm_direction = (a - b) / np.linalg.norm(a - b) 119 | ppd_dir = np.asarray([norm_direction[1], -norm_direction[0]]) 120 | 121 | ground_label = np.cast["int"](config["SEGMENTATION"]["GroundLabel"].split(',')) 122 | 123 | ratio = 10 124 | ppd_dir = ratio * ppd_dir 125 | point_check_list = copy.deepcopy(a) 126 | point_check_list = np.vstack([point_check_list, a - ppd_dir]) 127 | point_check_list = np.vstack([point_check_list, a + ppd_dir]) 128 | point_check_list = np.vstack([point_check_list, b]) 129 | point_check_list = np.vstack([point_check_list, b - ppd_dir]) 130 | point_check_list = np.vstack([point_check_list, b + ppd_dir]) 131 | point_check_list = np.vstack([point_check_list, middle]) 132 | point_check_list = np.vstack([point_check_list, middle - ppd_dir]) 133 | point_check_list = np.vstack([point_check_list, middle + ppd_dir]) 134 | rows, cols = seg_img.shape 135 | 136 | flag = False 137 | # count = 0 138 | for pcl in point_check_list: 139 | # swap the x,y coordinate 140 | y_int = np.cast["int"](pcl[0] + 0.5) 141 | x_int = np.cast["int"](pcl[1] + 0.5) 142 | if x_int < 0 or x_int > cols - 1 or y_int < 0 or y_int > rows - 1: 143 | # count = count + 1 144 | continue 145 | if seg_img[y_int, x_int] in ground_label: 146 | flag = True 147 | break 148 | # count = count + 1 149 | # continue 150 | # if count < 3: 151 | # flag = False 152 | 153 | return flag 154 | 155 | 156 | def check_if_roof_lines(seg_img, a, b, config)->bool: 157 | """ 158 | Check whether a line is on the roof of a building. 159 | :param seg_img: semantic segmentation image array 160 | :param a: end point of the line 161 | :param b: end point of the line 162 | :param config: configuration 163 | :return: 164 | """ 165 | 166 | middle = (a + b)/2.0 # middle point of the line 167 | norm_direction = (a - b) / np.linalg.norm(a - b) 168 | ppd_dir = np.asarray([norm_direction[1], -norm_direction[0]]) 169 | 170 | sky_label = int(config["SEGMENTATION"]["SkyLabel"]) 171 | 172 | ratio = 10 173 | ppd_dir = ratio * ppd_dir 174 | point_check_list = copy.deepcopy(a) 175 | point_check_list = np.vstack([point_check_list, a - ppd_dir]) 176 | point_check_list = np.vstack([point_check_list, a + ppd_dir]) 177 | point_check_list = np.vstack([point_check_list, b]) 178 | point_check_list = np.vstack([point_check_list, b - ppd_dir]) 179 | point_check_list = np.vstack([point_check_list, b + ppd_dir]) 180 | point_check_list = np.vstack([point_check_list, middle]) 181 | point_check_list = np.vstack([point_check_list, middle - ppd_dir]) 182 | point_check_list = np.vstack([point_check_list, middle + ppd_dir]) 183 | 184 | rows, cols = seg_img.shape 185 | 186 | flag = False 187 | # count = 0 188 | for pcl in point_check_list: 189 | # swap the x,y coordinate 190 | y_int = np.cast["int"](pcl[0] + 0.5) 191 | x_int = np.cast["int"](pcl[1] + 0.5) 192 | if x_int < 0 or x_int > cols - 1 or y_int < 0 or y_int > rows - 1: 193 | # count = count + 1 194 | continue 195 | if seg_img[y_int, x_int] == sky_label: 196 | flag = True 197 | break 198 | # count = count + 1 199 | # continue 200 | # if count < 3: 201 | # flag = False 202 | 203 | return flag 204 | 205 | 206 | def dist_comparaison(first_line, second_line, thres): 207 | """ 208 | Compare the distance of two lines 209 | :param first_line: 210 | :param second_line: 211 | :param thres: distance threshold 212 | :return: 213 | """ 214 | a_0 = copy.deepcopy(first_line[0]) 215 | b_0 = copy.deepcopy(first_line[1]) 216 | a_1 = copy.deepcopy(second_line[0]) 217 | b_1 = copy.deepcopy(second_line[1]) 218 | 219 | pt_0 = pointOnLine(a_0, b_0, (a_1+b_1)/2.0) 220 | dist_0 = np.linalg.norm(pt_0 - (a_1+b_1)/2.0) 221 | 222 | pt_1 = pointOnLine(a_1, b_1, (a_0 + b_0) / 2.0) 223 | dist_1 = np.linalg.norm(pt_1 - (a_0 + b_0) / 2.0) 224 | 225 | if dist_0 < thres or dist_1 < thres: 226 | a_1_refine = pointOnLine(a_0, b_0, a_1) 227 | b_1_refine = pointOnLine(a_0, b_0, b_1) 228 | 229 | if a_0[0] > a_1_refine[0]: 230 | a_0 = a_1_refine 231 | if b_0[0] < b_1_refine[0]: 232 | b_0 = b_1_refine 233 | 234 | return True, [a_0, b_0] 235 | 236 | return False, first_line 237 | 238 | 239 | def lineCoeff(p1, p2): 240 | """ 241 | Get the coefficients of a line from its two points 242 | :param p1: a point on the line 243 | :param p2: a point on the line 244 | :return: three coefficients 245 | """ 246 | A = (p1[1] - p2[1]) 247 | B = (p2[0] - p1[0]) 248 | C = (p1[0]*p2[1] - p2[0]*p1[1]) 249 | return A, B, -C 250 | 251 | 252 | def intersection(L1, L2): 253 | """ 254 | Get the intersection point of two lines 255 | :param L1: a line 256 | :param L2: a line 257 | :return: the intersection point 258 | """ 259 | D = L1[0] * L2[1] - L1[1] * L2[0] 260 | Dx = L1[2] * L2[1] - L1[1] * L2[2] 261 | Dy = L1[0] * L2[2] - L1[2] * L2[0] 262 | if D != 0: 263 | x = Dx / D 264 | y = Dy / D 265 | return x,y 266 | else: 267 | return False 268 | 269 | # def fittingVanshingPoints(lines): 270 | # dist = [] 271 | # for line in lines: 272 | # a = line[0] 273 | # b = line[1] 274 | # dist.append(np.linalg.norm(np.asarray([a-b]))) 275 | # ind = np.argsort(np.asarray(dist)) 276 | # max_num = np.min([5, len(ind)]) 277 | # A = [] 278 | # b = [] 279 | # for i in np.arange(-1, -max_num, -1): 280 | # if dist[i] < 10: 281 | # continue 282 | # l = lineCoeff(lines[i][0], lines[i][1]) 283 | # A.append([l[0], l[1]]) 284 | # b.append(l[2]) 285 | # A=np.asarray(A) 286 | # b=np.asarray(b) 287 | # # vpt = np.matmul(np.linalg.inv(np.matmul(np.transpose(A),A)),\ 288 | # # np.matmul(np.transpose(A),b)) 289 | # vpt = np.linalg.lstsq(A, b)[0].T 290 | # # vpt_verify = intersection([A[0][0],A[0][1], -b[0]], [A[1][0],A[1][1], -b[1]]) 291 | # return vpt 292 | 293 | 294 | def filter_lines_outof_building_ade20k(imgfile, lines, line_scores, segimg, vpts, config, use_vertical_vpt_only=0, verbose=True): 295 | """ 296 | Use the semantic segmentation results to filter the line segments and classify them into different groups. 297 | :param imgfile: the file name of the image 298 | :param lines: the line segments 299 | :param line_scores: the scores of the corresponding line segments 300 | :param segimg: the semantic segmentation image array 301 | :param vpts: the vanishing points 302 | :param config: configuration 303 | :param use_vertical_vpt_only: use only the vertical vanishing point to process the line segments 304 | :param verbose: when true, show the results 305 | :return: 306 | """ 307 | 308 | # initialize 309 | vert_lines = [] # vertical line segments 310 | hori0_lines = [] # horizontal line segments related to the first vanishing point 311 | hori1_lines = [] # horizontal line segments related to the second vanishing point 312 | 313 | # ######### filter the line segments out of buildings 314 | t_score = float(config["LINE_CLASSIFY"]["LineScore"]) # the threshold for the scores of lines 315 | for (a, b), s in zip(lines, line_scores): 316 | # only process the lines with scores over the threshold 317 | if s < t_score: 318 | continue 319 | is_in_building = check_if_line_lies_in_building_area(segimg, a, b, config) 320 | if not is_in_building: 321 | continue 322 | 323 | # classify line segments into different groups using their directions 324 | if use_vertical_vpt_only: 325 | is_vert = classifyWithVPTs(a, b, vpts[2], config) 326 | if is_vert: 327 | vert_lines.append([a, b]) 328 | else: 329 | is_hori0 = classifyWithVPTs(a, b, vpts[0], config) 330 | if is_hori0: 331 | hori0_lines.append([a, b]) 332 | is_hori1 = classifyWithVPTs(a, b, vpts[1], config) 333 | if (not is_hori0) and is_hori1: 334 | hori1_lines.append([a, b]) 335 | is_vert = classifyWithVPTs(a, b, vpts[2], config) 336 | if (not is_hori0) and (not is_hori1) and is_vert: 337 | vert_lines.append([a, b]) 338 | 339 | # if verbose: 340 | # plt.plot([a[1], b[1]], [a[0], b[0]], c=c(s), linewidth=2, zorder=s) 341 | # plt.scatter(a[1], a[0], **PLTOPTS) 342 | # plt.scatter(b[1], b[0], **PLTOPTS) 343 | 344 | # ######### refine and merge short vertical lines to long lines 345 | vert_line_refine = [] 346 | vert_line_merge = [] 347 | vert_lines_copy = copy.deepcopy(vert_lines) 348 | 349 | # use vanishing point to refine the vertical lines 350 | for line in vert_lines_copy: 351 | a = line[0] 352 | b = line[1] 353 | line = lineRefinementWithVPT([a, b], np.asarray([vpts[2, 1], vpts[2, 0]])) 354 | vert_line_refine.append(line) 355 | 356 | # merge short lines 357 | for i in range(len(vert_line_refine)): 358 | linesi = vert_line_refine[i] 359 | lens = np.linalg.norm(linesi[0] - linesi[1]) 360 | if linesi[0][0] < 0 and linesi[1][0] < 0 or lens < 10: 361 | continue 362 | for j in range(i+1, len(vert_line_refine)): 363 | # compare the distance between the two lines 364 | linesj = vert_line_refine[j] 365 | if linesj[0][0] < 0 and linesj[1][0] < 0: 366 | continue 367 | is_merging, vert_line_refine[i] = dist_comparaison(vert_line_refine[i], vert_line_refine[j], 5) 368 | if is_merging: 369 | vert_line_refine[j] = [np.asarray([-1, -1]), np.asarray([-1, -1])] 370 | 371 | for line in vert_line_refine: 372 | a = line[0] 373 | b = line[1] 374 | if a[1] < 0: 375 | continue 376 | vert_line_merge.append(line) 377 | if verbose: 378 | plt.plot([a[1], b[1]], [a[0], b[0]], c='b', linewidth=2) 379 | plt.scatter(a[1], a[0], **PLTOPTS) 380 | plt.scatter(b[1], b[0], **PLTOPTS) 381 | 382 | 383 | # # ######### classify horizontal lines ## this may be used in future work 384 | # bottom_lines = [] # bottom lines of buildings 385 | # roof_lines =[] # roof lines of buildings 386 | # 387 | # if not use_vertical_vpt_only: 388 | # for line in hori0_lines: 389 | # a = line[0] 390 | # b = line[1] 391 | # flag = check_if_roof_lines(segimg, line[0], line[1], config) 392 | # if flag: 393 | # if verbose: 394 | # plt.plot([a[1], b[1]], [a[0], b[0]], c='r', linewidth=2) 395 | # plt.scatter(a[1], a[0], **PLTOPTS) 396 | # plt.scatter(b[1], b[0], **PLTOPTS) 397 | # roof_lines.append(line) 398 | # continue 399 | # flag = check_if_bottom_lines(segimg, line[0], line[1], config) 400 | # if flag: 401 | # if verbose: 402 | # plt.plot([a[1], b[1]], [a[0], b[0]], c='g', linewidth=2) 403 | # plt.scatter(a[1], a[0], **PLTOPTS) 404 | # plt.scatter(b[1], b[0], **PLTOPTS) 405 | # bottom_lines.append(line) 406 | # continue 407 | # 408 | # for line in hori1_lines: 409 | # a = line[0] 410 | # b = line[1] 411 | # flag = check_if_roof_lines(segimg, line[0], line[1], config) 412 | # if flag: 413 | # if verbose: 414 | # plt.plot([a[1], b[1]], [a[0], b[0]], c='r', linewidth=2) 415 | # plt.scatter(a[1], a[0], **PLTOPTS) 416 | # plt.scatter(b[1], b[0], **PLTOPTS) 417 | # roof_lines.append(line) 418 | # continue 419 | # flag = check_if_bottom_lines(segimg, line[0], line[1], config) 420 | # if flag: 421 | # if verbose: 422 | # plt.plot([a[1], b[1]], [a[0], b[0]], c='g', linewidth=2) 423 | # plt.scatter(a[1], a[0], **PLTOPTS) 424 | # plt.scatter(b[1], b[0], **PLTOPTS) 425 | # bottom_lines.append(line) 426 | # continue 427 | 428 | if verbose: 429 | # plt.show() 430 | plt.close() 431 | 432 | # return vert_line_merge, bottom_lines, roof_lines 433 | return vert_line_merge 434 | 435 | 436 | def clausterLinesWithCenters(ht_set, config, using_height=False): 437 | """ 438 | Use DBSAN algorithm to divide the line segments and their heights into groups. 439 | :param ht_set: the list of heights 440 | :param config: configuration 441 | :param using_height: if value is '1', use the heights in the grouping 442 | :return: 443 | """ 444 | 445 | X = [] 446 | if using_height: 447 | for ht, a, b, *_ in ht_set: 448 | X.append([(a[0] + b[0])/2, (a[1] + b[1])/2, ht]) 449 | else: 450 | for ht, a, b, *_ in ht_set: 451 | X.append([(a[0] + b[0])/2, (a[1] + b[1])/2]) 452 | X = np.asarray(X) 453 | 454 | max_DBSAN_dist = float(config["HEIGHT_MEAS"]["MaxDBSANDist"]) 455 | 456 | try: 457 | clustering = DBSCAN(eps=max_DBSAN_dist, min_samples=1).fit(X) 458 | except: 459 | print("!!! error in clustering: Expected 2D array, got 1D array instead. Return no results") 460 | clustered_lines = None 461 | return clustered_lines 462 | 463 | clustered_lines = [] 464 | max_val = np.max(clustering.labels_)+1 465 | for label in range(max_val): 466 | new_list = [] 467 | new_ht_list = [] 468 | for i in range(len(clustering.labels_)): 469 | if clustering.labels_[i] == label: 470 | new_list.append(ht_set[i]) 471 | new_ht_list.append(ht_set[i][0]) 472 | medi_val = np.median(np.asarray(new_ht_list)) # the median height of the group 473 | mean_val = np.mean(np.asarray(new_ht_list)) # the mean height of the group 474 | new_list.append(medi_val) 475 | new_list.append(mean_val) 476 | clustered_lines.append(new_list) 477 | 478 | return clustered_lines 479 | -------------------------------------------------------------------------------- /heightMeasurement.py: -------------------------------------------------------------------------------- 1 | #-*- encoding:utf-8 -*- 2 | 3 | import matplotlib.pyplot as plt 4 | import numpy as np 5 | from lineClassification import * 6 | from lineDrawingConfig import * 7 | from lineRefinement import * 8 | from filesIO import * 9 | import skimage.io 10 | import copy 11 | 12 | 13 | def gt_measurement(zgt_img, a, b, verbose=False): 14 | """ 15 | If there is a ground truth image with each pixel value represents the vertical z value, the ground truth height of a 16 | vertical line can be measured. 17 | :param zgt_img: the ground truth image with z values 18 | :param a: the up point of a vertical line segment 19 | :param b: the bottom point of a vertical line segment 20 | :param verbose: when true, show the results 21 | :return: 22 | """ 23 | 24 | if a[1] > b[1]: 25 | temp = copy.deepcopy(a) 26 | a = b 27 | b = temp 28 | 29 | # check the a point & b point 30 | a = np.cast["int"](a + [0.5, 0.5]) 31 | b = np.cast["int"](b + [0.5, 0.5]) 32 | 33 | rows, cols = zgt_img.shape 34 | 35 | row_check = lambda x : min(rows - 1, max(0, x)) 36 | cols_check = lambda x: min(cols - 1, max(0, x)) 37 | pt_check = lambda pt:np.asarray([cols_check(pt[0]), row_check(pt[1])]) 38 | 39 | a = pt_check(a) 40 | b = pt_check(b) 41 | 42 | gt_org = 0 43 | gt_expd= 0 44 | 45 | if zgt_img[a[1],a[0]] == 0 or zgt_img[b[1],b[0]] == 0: 46 | gt_org = 0 47 | else: 48 | gt_org = abs(zgt_img[a[1],a[0]] - zgt_img[b[1],b[0]]) 49 | 50 | direction = (a-b)/np.linalg.norm(a - b) 51 | 52 | b_expd = copy.deepcopy(b) 53 | count = 1 54 | while zgt_img[b_expd[1], b_expd[0]] == 0 and a[1] < b_expd[1]: 55 | b_expd = np.cast["int"](b + count*direction) 56 | count = count + 1 57 | a_expd = copy.deepcopy(a) 58 | count = 1 59 | 60 | if zgt_img[a_expd[1],a_expd[0]] == 0: 61 | while a_expd[0]>0 and a_expd[0] <= cols -1 and a_expd[1] <=rows - 1 and zgt_img[a_expd[1],a_expd[0]] == 0: 62 | a_expd = np.cast["int"](a - count*direction) 63 | count = count + 1 64 | else: 65 | while a_expd[0]>0 and a_expd[0] <= cols -1 and a_expd[1] >=0 and zgt_img[a_expd[1],a_expd[0]]!= 0: 66 | a_expd = np.cast["int"](a + count*direction) 67 | count = count + 1 68 | a_expd = np.cast["int"](a + (count-2)*direction) 69 | pass 70 | gt_expd = abs(zgt_img[a_expd[1],a_expd[0]] - zgt_img[b_expd[1],b_expd[0]]) 71 | 72 | if verbose: 73 | print("here---------------:") 74 | print(a_expd) 75 | print(b_expd) 76 | print(zgt_img[a_expd[1],a_expd[0]]) 77 | print(gt_org,gt_expd) 78 | 79 | if verbose: 80 | plt.close() 81 | plt.figure() 82 | plt.imshow(zgt_img) 83 | plt.plot([a[0], b[0]], [a[1], b[1]], c=c(0), linewidth=2) 84 | plt.scatter(a[0], a[1], **PLTOPTS) 85 | plt.scatter(b[0], b[1], **PLTOPTS) 86 | # plt.show() 87 | 88 | return gt_org, gt_expd 89 | 90 | 91 | def sv_measurement(v1, v2, v3, x1, x2, zc = 2.5): 92 | """ 93 | Use single-view metrology and three vanishing points to calculate height. 94 | :param v1: vanishing point on the horizontal vanishing line 95 | :param v2: vanishing point on the horizontal vanishing line 96 | :param v3: vertical vanishing point 97 | :param x1: bottom point of the vertical line segment 98 | :param x2: top point of the vertical line segment 99 | :param zc: camera height, unit is meter 100 | :return: height zx 101 | """ 102 | 103 | vline = np.cross(v1, v2) 104 | p4 = vline / np.linalg.norm(vline) 105 | 106 | zc = zc * np.linalg.det([v1, v2, v3]) 107 | alpha = -np.linalg.det([v1, v2, p4]) / zc # the scalar 108 | p3 = alpha * v3 109 | 110 | # rho = np.dot(x1, p4)/(1 + zc * np.dot(p3, p4)) 111 | # zx = -np.linalg.norm(np.cross(x1, x2))/(rho * np.linalg.norm(np.cross(p3, x2))) 112 | 113 | zx = -np.linalg.norm(np.cross(x1, x2)) / (np.dot(p4, x1) * np.linalg.norm(np.cross(p3, x2))) 114 | zx = abs(zx) # v1 and v2 may exchange (vanishing line can have two directions) 115 | 116 | return zx 117 | 118 | 119 | def sv_measurement1(v, vline, x1, x2, zc=2.5): 120 | """ 121 | Use single-view metrology and vertical vanishing point along with horizontal vanishing line to calculate height. 122 | :param v: vertical vanishing point 123 | :param vline: horizontal vanishing line 124 | :param x1: bottom point of the vertical line segment 125 | :param x2: top point of the vertical line segment 126 | :param zc: camera height, unit is meter 127 | :return: height zx 128 | """ 129 | 130 | p4 = vline / np.linalg.norm(vline) 131 | 132 | alpha = -1 / (np.dot(p4, v) * zc) # the scalar 133 | p3 = alpha * v 134 | 135 | # rho = np.dot(x1, p4) / (1 + zc * np.dot(p3, p4)) 136 | # zx = -np.linalg.norm(np.cross(x1, x2)) / (rho * np.linalg.norm(np.cross(p3, x2))) 137 | 138 | zx = -np.linalg.norm(np.cross(x1, x2)) / (np.dot(p4, x1) * np.linalg.norm(np.cross(p3, x2))) 139 | zx = abs(zx) # vanishing line can have two directions 140 | 141 | return zx 142 | 143 | 144 | def singleViewMeasWithCrossRatio(hori_v1, hori_v2, vert_v1, pt_top, pt_bottom, zc=2.5): 145 | """ 146 | Use single-view metrology and three vanishing points to calculate height. The function has the same effect as 147 | "sv_measurement()", with a different calculation method. Pay attention to x, y order of the input. 148 | :param hori_v1: image coordinates of a vanishing point on the horizontal vanishing line 149 | :param hori_v2: image coordinates of a vanishing point on the horizontal vanishing line 150 | :param vert_v1: image coordinates of the vertical vanishing point 151 | :param pt_top: bottom point of the vertical line segment 152 | :param pt_bottom: top point of the vertical line segment 153 | :param zc: camera height, unit is meter 154 | :return: height 155 | """ 156 | line_vl = lineCoeff(hori_v1, hori_v2) 157 | line_building_vert = lineCoeff(pt_top, pt_bottom) 158 | C = intersection(line_vl, line_building_vert) 159 | 160 | dist_AC = np.linalg.norm(np.asarray([vert_v1 - C])) 161 | dist_AB = np.linalg.norm(np.asarray([vert_v1 - pt_top])) 162 | dist_BD = np.linalg.norm(np.asarray([pt_top - pt_bottom])) 163 | dist_CD = np.linalg.norm(np.asarray([C - pt_bottom])) 164 | 165 | height = dist_BD*dist_AC/(dist_CD*dist_AB)*zc 166 | return height 167 | 168 | 169 | def singleViewMeasWithCrossRatio_vl(hori_vline, vert_v1, pt_top, pt_bottom, zc=2.5): 170 | """ 171 | Use single-view metrology and vertical vanishing point along with horizontal vanishing line to calculate height. 172 | The function has the same effect as "sv_measurement1()", with a different calculation method. 173 | Pay attention to x, y order of the input. 174 | :param hori_vline: image coordinates of the horizontal vanishing line 175 | :param vert_v1: image coordinates of the vertical vanishing point 176 | :param pt_top: bottom point of the vertical line segment 177 | :param pt_bottom: top point of the vertical line segment 178 | :param zc: camera height, unit is meter 179 | :return: height 180 | """ 181 | 182 | line_vl = hori_vline 183 | line_building_vert = lineCoeff(pt_top, pt_bottom) 184 | C = intersection(line_vl, line_building_vert) 185 | 186 | dist_AC = np.linalg.norm(np.asarray([vert_v1 - C])) 187 | dist_AB = np.linalg.norm(np.asarray([vert_v1 - pt_top])) 188 | dist_BD = np.linalg.norm(np.asarray([pt_top - pt_bottom])) 189 | dist_CD = np.linalg.norm(np.asarray([C - pt_bottom])) 190 | 191 | height = dist_BD*dist_AC/(dist_CD*dist_AB)*zc 192 | return height 193 | 194 | 195 | def vp_calculation_with_pitch(w, h, pitch, focal_length): 196 | """ 197 | Calculate the vertical vanishing point and the horizontal vanishing line through pitch angle. Note: this function is 198 | specially set for street view images with known rotation angles (pitch, yaw, and roll), e.g. Google street view. 199 | Normally, the roll angle is zero. The pitch is used for the calculation. 200 | :param w: image width 201 | :param h: image height 202 | :param pitch: pitch angle 203 | :param focal_length: focal length 204 | :return: v, the vertical vanishing point, and vline, the horizontal vanishing line 205 | """ 206 | 207 | # initialize 208 | v = np.array([w / 2, 0.0, 1.0]) # pitch will influence the second element of v, v[1] 209 | vline = np.array([0.0, 1.0, 0.0]) # pitch will influence the third element of vline, vline[2] 210 | 211 | if pitch == 0: 212 | v[:] = [0, -1, 0] 213 | vline[:] = [0, 1, h / 2] 214 | else: 215 | v[1] = h / 2 - (focal_length / np.tan(np.deg2rad(pitch))) 216 | vline[2] = (h / 2 + focal_length * np.tan(np.deg2rad(pitch))) 217 | 218 | # print(v) 219 | # print(vline) 220 | 221 | return v, vline 222 | 223 | 224 | def heightCalc(fname_dict, intrins, config, img_size=None, pitch=None, use_pitch_only=0, use_detected_vpt_only=0, verbose=False): 225 | """ 226 | Estimate the height of buildings. 227 | :param fname_dict: the dictionary of file names 228 | :param intrins: the intrinsic matrix 229 | :param config: the configuration 230 | :param img_size: the size of the image 231 | :param pitch: the pitch angle of the image 232 | :param use_pitch_only: when the value is '1', use only the pitch angle to calculate vanishing line and vertical vanishing point 233 | :param use_detected_vpt_only: when the value is '1', use only the detected vanishing points 234 | :param verbose: when true, show the results 235 | :return: 236 | """ 237 | 238 | if img_size is None: 239 | img_size = [640, 640] 240 | 241 | try: 242 | vpt_fname = fname_dict["vpt"] 243 | img_fname = fname_dict["img"] 244 | line_fname = fname_dict["line"] 245 | seg_fname = fname_dict["seg"] 246 | zgt_fname = fname_dict["zgt"] 247 | 248 | # ######### get the vanishing points 249 | w = img_size[0] 250 | h = img_size[1] 251 | focal_length = intrins[0, 0] 252 | if use_pitch_only: 253 | # initialize the vanishing points 254 | # note: vps is set to keep the same format as the detected vps 255 | # and only vps[2] (the vertical vanishing point) is used together with the vanishing line 256 | vps = np.zeros([3, 2]) 257 | 258 | # calculate the vertical vanishing point and the vanishing line 259 | vertical_v, vline = vp_calculation_with_pitch(w, h, pitch, focal_length) 260 | 261 | # transformation of vps to 2D image coordinates 262 | if vertical_v[2] == 0: # a special case 263 | vertical_v[0] = 320 264 | vertical_v[1] = -9999999 265 | vps[2, :] = vertical_v[:2] 266 | 267 | elif '.npz' in vpt_fname: 268 | vps = load_vps_2d(vpt_fname) 269 | 270 | # if not 'use_detected_vpt_only', use the calculated vertical vanishing point to replace the detected one 271 | if not use_detected_vpt_only: 272 | vertical_v, vline = vp_calculation_with_pitch(w, h, pitch, focal_length) 273 | 274 | # transformation of vps to 2D image coordinates 275 | if vertical_v[2] == 0: # a special case 276 | vertical_v[0] = 320 277 | vertical_v[1] = -9999999 278 | vps[2, :] = vertical_v[:2] 279 | 280 | # ######### get the detected line segments and the semantic segmentation results 281 | line_segs, scores = load_line_array(line_fname) 282 | seg_img = load_seg_array(seg_fname) 283 | 284 | # save the visualization of the line/segmentation results 285 | org_image = skimage.io.imread(img_fname) 286 | for i, t in enumerate([0.94]): # lines with different score thresholds ([0.94, 0.95, 0.96, 0.97, 0.98, 0.99]) 287 | plt.gca().set_axis_off() 288 | plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0) 289 | plt.margins(0, 0) 290 | for (a, b), s in zip(line_segs, scores): 291 | if s < t: 292 | continue 293 | plt.plot([a[1], b[1]], [a[0], b[0]], c=c(s), linewidth=2, zorder=s) 294 | plt.scatter(a[1], a[0], **PLTOPTS) 295 | plt.scatter(b[1], b[0], **PLTOPTS) 296 | plt.gca().xaxis.set_major_locator(plt.NullLocator()) 297 | plt.gca().yaxis.set_major_locator(plt.NullLocator()) 298 | plt.imshow(org_image) 299 | plt.imshow(seg_img, alpha=0.5) 300 | 301 | # show the vanishing points and vanishing line 302 | if use_pitch_only: 303 | x, y = vertical_v[:2] 304 | plt.scatter(x, y) 305 | plt.plot([0, w], [vline[2], vline[2]], c='b', linewidth=5) 306 | else: 307 | for i in range(len(vps)): 308 | x, y = vps[i] 309 | plt.scatter(x, y) 310 | 311 | integrated_save_name = img_fname.replace(".jpg", f"-{t:.02f}_inls.svg") 312 | integrated_save_name = integrated_save_name.replace("imgs", "inls") 313 | integrated_save_dir = os.path.dirname(integrated_save_name) 314 | if not os.path.exists(integrated_save_dir): 315 | os.makedirs(integrated_save_dir) 316 | # plt.show() 317 | plt.close() 318 | 319 | # ######### processing the line segments 320 | if verbose: 321 | plt.close() 322 | org_img = skimage.io.imread(img_fname) 323 | plt.imshow(org_img) 324 | plt.imshow(seg_img, alpha=0.5) 325 | 326 | # classify the line segments and extend vertical lines 327 | verticals = filter_lines_outof_building_ade20k(img_fname, line_segs, scores, seg_img, vps, config, use_pitch_only) 328 | verticals = verticalLineExtending(img_fname, verticals, seg_img, [vps[2, 1], vps[2, 0]], config) 329 | # verticals, bottoms, roofs = filter_lines_outof_building_ade20k(img_fname, line_segs, scores, seg_img, vps, config, use_pitch_only) 330 | # verticals = verticalLineExtendingWithBRLines(img_fname, verticals, roofs, bottoms, seg_img, config) 331 | 332 | # ######### calculate heights of processed vertical line segments 333 | invK = np.linalg.inv(intrins) 334 | ht_set = [] 335 | check_list = [] 336 | 337 | for line in verticals: 338 | # only consider a, b as integers 339 | # a = np.cast["int"](line[0] + 0.5) 340 | # b = np.cast["int"](line[1] + 0.5) 341 | a = line[0] 342 | b = line[1] 343 | 344 | # remove duplicate a,b because of integer 345 | if len(check_list) !=0 : 346 | flag = 0 347 | for a0,a1,b0,b1 in check_list: 348 | if a0 == a[0] and a1 == a[1] and b0 == b[0] and b1 == b[1]: 349 | flag=1 350 | break 351 | if flag: 352 | continue 353 | check_list.append([a[0], a[1], b[0], b[1]]) 354 | 355 | # swap x and y, as coordinates here are expressed in [y, x] order 356 | a_d3 = np.asarray([a[1], a[0], 1]) 357 | a_d3 = np.matmul(invK, np.transpose(a_d3)) 358 | 359 | b_d3 = np.asarray([b[1], b[0], 1]) 360 | b_d3 = np.matmul(invK, np.transpose(b_d3)) 361 | 362 | if use_detected_vpt_only: 363 | vps0 = np.asarray([vps[0, 0], vps[0, 1], 1]) 364 | vps1 = np.asarray([vps[1, 0], vps[1, 1], 1]) 365 | 366 | use_horizontal_property_to_refine = 0 367 | if use_horizontal_property_to_refine: 368 | vps0 = np.asarray([vps[0, 0], (vps[0, 1] + vps[1, 1]) / 2.0, 1]) 369 | vps1 = np.asarray([vps[1, 0], (vps[0, 1] + vps[1, 1]) / 2.0, 1]) 370 | 371 | vps0 = np.matmul(invK, np.transpose(vps0)) 372 | vps1 = np.matmul(invK, np.transpose(vps1)) 373 | 374 | vps2 = np.asarray([vps[2, 0], vps[2, 1], 1]) 375 | vps2 = np.matmul(invK, np.transpose(vps2)) 376 | ht = sv_measurement(vps0, vps1, vps2, b_d3, a_d3, zc=float(config["STREET_VIEW"]["CameraHeight"])) 377 | else: 378 | ht = singleViewMeasWithCrossRatio_vl(vline, vertical_v[:2], np.asarray([a[1], a[0]]), 379 | np.asarray([b[1], b[0]]), 380 | zc=float(config["STREET_VIEW"]["CameraHeight"])) 381 | 382 | gt_exist = int(config["GROUND_TRUTH"]["Exist"]) 383 | if gt_exist: 384 | zgt_img = load_zgts(zgt_fname) 385 | ht_gt_org, ht_gt_expd = gt_measurement(zgt_img,np.asarray([a[1], a[0]]), np.asarray([b[1], b[0]])) 386 | else: 387 | ht_gt_org, ht_gt_expd = ht*0, ht*0 388 | ht_set.append([ht, a, b, ht_gt_org, ht_gt_expd]) 389 | 390 | if verbose: 391 | plt.close() 392 | # plt.figure(figsize=(18, 8)) 393 | # plt.subplot(121) 394 | plt.figure(figsize=(10, 8)) 395 | org_img = skimage.io.imread(img_fname) 396 | plt.imshow(org_img) 397 | plt.imshow(seg_img, alpha=0.5) 398 | print("path:%s" % img_fname) 399 | 400 | # divide vertical line segments into groups using computed heights 401 | grouped_lines = clausterLinesWithCenters(ht_set, config, using_height=True) 402 | if grouped_lines is None: 403 | print('no suitable vertical lines founded in image ' + img_fname) 404 | return 405 | 406 | list_len = len(grouped_lines) 407 | heights = [] 408 | ax_legends = [] 409 | if len(colors_tables) < list_len: 410 | print("warning: lines with the same color might be different groups.") 411 | for i in range(list_len): 412 | lines = grouped_lines[i] 413 | list_len_lines = len(lines) 414 | # rng = np.random.default_rng() 415 | # colors = np.cast['float'](rng.integers(255, size=3)) 416 | # colors = colors / (np.linalg.norm(colors) + 0.0001) 417 | heights.append([lines[-2], lines[-1]]) 418 | for j in range(list_len_lines - 2): 419 | # plot points 420 | a = lines[j][1] 421 | b = lines[j][2] 422 | if verbose: 423 | ax_line, = plt.plot([a[1], b[1]], [a[0], b[0]], c=colors_tables[i % len(colors_tables)], linewidth=2) 424 | plt.scatter(a[1], a[0], **PLTOPTS) 425 | plt.scatter(b[1], b[0], **PLTOPTS) 426 | ax_legends.append(ax_line) 427 | 428 | if verbose: 429 | plt.legend(ax_legends, ['average_height = %.4fm, median_height = %.4fm' % (y, x) for x, y in heights]) 430 | # plt.legend(ax_legends, ['median_height = %.4fm, average_height = %.4fm' % (x, y) for x, y in heights]) 431 | result_save_name = img_fname.replace('imgs', 'ht_results') 432 | result_save_name = result_save_name.replace('.jpg', '_htre.svg') 433 | result_save_name2 = result_save_name.replace('.svg', '.png') 434 | re_save_dir = os.path.dirname(result_save_name) 435 | if not os.path.exists(re_save_dir): 436 | os.makedirs(re_save_dir) 437 | plt.savefig(result_save_name, bbox_inches="tight") 438 | plt.savefig(result_save_name2, bbox_inches="tight") 439 | # plt.show() 440 | plt.close() 441 | 442 | pass 443 | 444 | except IOError: 445 | print("file does not exist\n") 446 | --------------------------------------------------------------------------------