├── Detection-Example-A3.ipynb
├── Detection-Example.ipynb
├── README.md
├── extras
├── graphics
│ ├── 3D_Convolution_Animation.gif
│ ├── PPE_logo.jpg
│ ├── dataset.png
│ ├── demo.gif
│ ├── methods.jpg
│ ├── results.jpg
│ ├── yolo.jpg
│ ├── yolo_complete.jpg
│ ├── yolo_conv_block.jpg
│ ├── yolo_output_block.jpg
│ └── yolo_residual_block.jpg
└── sample-images
│ ├── 0.JPG
│ ├── 1.JPG
│ ├── 10.jpg
│ ├── 2.jpg
│ ├── 3.jpg
│ ├── 4.jpg
│ ├── 5.jpg
│ ├── 6.JPG
│ ├── 7.JPG
│ ├── 8.jpg
│ └── 9.jpg
├── model-data
└── weights
│ └── readme.md
├── src
├── __init__.py
├── utils
│ ├── __init__.py
│ ├── datagen.py
│ ├── fixes.py
│ └── image.py
└── yolo3
│ ├── __init__.py
│ ├── detect.py
│ └── model.py
└── tutorials
├── Build-YOLO-Model.ipynb
└── Object-Detection.ipynb
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # **Real-time Detection of Personal-Protective-Equipment (PPE)**
4 |
5 |
6 |
7 | ## **Table of Contents**
8 | 1. [Introduction](#introduction)
9 | 2. [Implementation](#implementation)
10 | 3. [Dataset](#dataset)
11 | 4. [Pre-trained models](#pre-trained-models)
12 | 5. [Tutorials](#tutorials)
13 |
14 | ## **Introduction**
15 |
16 | The repository presents Tensorflow 2.0 (Keras) implementation of real-time detection of PPE (e.g., hard hat, safety vest) compliances of workers. The detailed procedures can be found in the following paper.
17 |
18 | ### Article
19 |
20 | [**Deep Learning for Site Safety: Real-Time Detection of Personal Protective Equipment**](https://www.sciencedirect.com/science/article/abs/pii/S0926580519308325)\
21 | Nipun D. Nath, Amir H. Behzadan, Stephanie G. Paal \
22 | Automation in Construction 112, pp. 103085
23 |
24 | Please cite the article if you use the dataset, model or method(s), or find the article useful in your research. Thank you!
25 |
26 | ### LaTeX citation:
27 |
28 | @article{Nath2020,
29 | author = {Nipun D. Nath and Amir H. Behzadan and Stephanie G. Paal},
30 | title = {Deep Learning for Site Safety: Real-Time Detection of Personal Protective Equipment},
31 | journal = {Automation in Construction},
32 | volume = {112},
33 | year = {2020},
34 | pages = {103085}
35 |
36 |
37 | ## **Implementation**
38 |
39 | ### Dependencies
40 | - `tensorflow 2.0`
41 | - `numpy`
42 | - `opencv`
43 | - `matplotlib`
44 | - `pandas`
45 |
46 | Please follow the [`Detection-Example.ipynb`](https://github.com/nipundebnath/pictor-ppe/blob/master/Detection-Example.ipynb) notebook to test the models.
47 |
48 | ## **Dataset**
49 |
50 | ### Dataset statisctics
51 |
52 | The dataset (named **Pictor-v3**) contains 774 crowd-sourced and 698 web-mined images. Crowd-sourced and web-mined images contain 2,496 and 2,230 instances of workers, respectively. The dataset is available [here](https://drive.google.com/drive/folders/1akhyTNVrkqMMcIFUQCEbW5ehfmG0CdYH?usp=sharing). A brief statistics of the dataset is shown in the following figure.
53 |
54 |
55 |
56 | ### Annotation example
57 |
58 | **@TODO:** Please stay tuned!
59 |
60 | ### Download the crowd-sourced dataset
61 |
62 | The crowd-sourced images and annotated labels can be accessed from the corresponding subfolders in the dataset.
63 |
64 | ## **Methods/Approaches**
65 |
66 | The paper presents three different approaches for verifying PPE compliance:
67 |
68 |
69 |
70 | **Approach-1**: YOLO-v3-A1 model detects worker, hat, and vest (three object classes) individually. Next, ML classifiers (Neural Network, Decision Tree) classify each worker as W (wearing no hat or vest), WH (wearing only hat), WV (wearing only vest), or WHV (wearing both hat and vest).
71 |
72 | **Approach-2**: YOLO-v3-A2 model localizes workers in the input image and directly classifies each detected worker as W, WH, WV, or WHV.
73 |
74 | **Approach-3**: YOLO-v3-A3 model first detects all workers in the input image and then, a CNN-based classifier model (VGG-16, ResNet-50, Xception) was applied to the cropped worker images to classify the detected worker as W, WH, WV, or WHV.
75 |
76 | ## **Results**
77 |
78 | - The mean-average-precision (mAP) of YOLO models in detecting classes:
79 |
80 |
81 |
82 | - The overall mAP of verifying workers' PPE compliance:
83 |
84 |
85 |
86 | - The best is Approach-2 (72.3% mAP).
87 |
88 | ## **Pre-trained Models**
89 |
90 | You can download the models trained on Pictor-v3 dataset from the "Models" folder in the dataset. These models include:
91 |
92 | - YOLO-v3-A1
93 | - YOLO-v3-A2
94 | - YOLO-v3-A3
95 | - ML Classifiers (Approach-1)
96 | - CNN Classifiers (Approach-3)
97 |
98 | ## **Tutorials**
99 |
100 | Please follow the notebooks in [tutorials](https://github.com/nipundebnath/pictor-ppe/blob/master/tutorials/) folder to learn more about:
101 | - building YOLO model from scratch using tensorflow 2.0
102 | - interpret YOLO output and convert to bounding boxes with class label and confidence score.
103 |
104 | **@TODO:** Please stay tuned! More tutorials are coming soon.
105 |
--------------------------------------------------------------------------------
/extras/graphics/3D_Convolution_Animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/graphics/3D_Convolution_Animation.gif
--------------------------------------------------------------------------------
/extras/graphics/PPE_logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/graphics/PPE_logo.jpg
--------------------------------------------------------------------------------
/extras/graphics/dataset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/graphics/dataset.png
--------------------------------------------------------------------------------
/extras/graphics/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/graphics/demo.gif
--------------------------------------------------------------------------------
/extras/graphics/methods.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/graphics/methods.jpg
--------------------------------------------------------------------------------
/extras/graphics/results.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/graphics/results.jpg
--------------------------------------------------------------------------------
/extras/graphics/yolo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/graphics/yolo.jpg
--------------------------------------------------------------------------------
/extras/graphics/yolo_complete.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/graphics/yolo_complete.jpg
--------------------------------------------------------------------------------
/extras/graphics/yolo_conv_block.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/graphics/yolo_conv_block.jpg
--------------------------------------------------------------------------------
/extras/graphics/yolo_output_block.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/graphics/yolo_output_block.jpg
--------------------------------------------------------------------------------
/extras/graphics/yolo_residual_block.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/graphics/yolo_residual_block.jpg
--------------------------------------------------------------------------------
/extras/sample-images/0.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/sample-images/0.JPG
--------------------------------------------------------------------------------
/extras/sample-images/1.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/sample-images/1.JPG
--------------------------------------------------------------------------------
/extras/sample-images/10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/sample-images/10.jpg
--------------------------------------------------------------------------------
/extras/sample-images/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/sample-images/2.jpg
--------------------------------------------------------------------------------
/extras/sample-images/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/sample-images/3.jpg
--------------------------------------------------------------------------------
/extras/sample-images/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/sample-images/4.jpg
--------------------------------------------------------------------------------
/extras/sample-images/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/sample-images/5.jpg
--------------------------------------------------------------------------------
/extras/sample-images/6.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/sample-images/6.JPG
--------------------------------------------------------------------------------
/extras/sample-images/7.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/sample-images/7.JPG
--------------------------------------------------------------------------------
/extras/sample-images/8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/sample-images/8.jpg
--------------------------------------------------------------------------------
/extras/sample-images/9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/extras/sample-images/9.jpg
--------------------------------------------------------------------------------
/model-data/weights/readme.md:
--------------------------------------------------------------------------------
1 | Download the trained weights of YOLO models ([Google Drive folder](https://drive.google.com/drive/folders/13tCdROHnS0c5VibW1VO8pOEj0rXEvvGj?usp=sharing)) and put in this folder.
2 |
--------------------------------------------------------------------------------
/src/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/src/__init__.py
--------------------------------------------------------------------------------
/src/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ciber-lab/pictor-ppe/8f41c9525bbd1f45245f593c7502a1934eef06fa/src/utils/__init__.py
--------------------------------------------------------------------------------
/src/utils/datagen.py:
--------------------------------------------------------------------------------
1 | import cv2
2 | import numpy as np
3 |
4 | from matplotlib.colors import rgb_to_hsv, hsv_to_rgb
5 |
6 |
7 | def rand(a=0, b=1):
8 | return np.random.rand()*(b-a) + a
9 |
10 |
11 | def get_random_data (
12 | annotation_line,
13 | input_shape,
14 | max_boxes=25,
15 | scale=.3,
16 | hue=.1,
17 | sat=1.5,
18 | val=1.5,
19 | random=True
20 | ):
21 |
22 | '''
23 | random preprocessing for real-time data augmentation
24 | '''
25 |
26 | line = annotation_line.split('\t')
27 | h, w = input_shape
28 | box = np.array([np.array(list(map(int,box.split(',')))) for box in line[1:]])
29 |
30 | image = cv2.imread(line[0])
31 | ih, iw, ic = image.shape
32 |
33 | if not random:
34 | resize_scale = min(h/ih, w/iw)
35 |
36 | nw = int(iw * resize_scale)
37 | nh = int(ih * resize_scale)
38 |
39 | max_offx = w - nw
40 | max_offy = h - nh
41 |
42 | dx = max_offx//2
43 | dy = max_offy//2
44 |
45 | to_x0, to_y0 = max(0, dx), max(0, dy)
46 | from_x0, from_y0 = max(0, -dx), max(0, -dy)
47 | wx, hy = min(w, dx+nw) - to_x0, min(h, dy+nh) - to_y0
48 |
49 | # place image
50 | image_data = np.zeros((*input_shape,ic), dtype='uint8') + 128
51 | image_data[to_y0:to_y0+hy, to_x0:to_x0+wx, :] = cv2.resize(image, (nw, nh))[from_y0:from_y0+hy, from_x0:from_x0+wx, :]
52 |
53 | flip = False
54 | image_data = image_data/255.
55 | else:
56 | if np.random.uniform() >= 0.5:
57 | # scale Up
58 | resize_scale = 1. + scale * np.random.uniform()
59 | resize_scale = max( h*resize_scale/ih, w*resize_scale/iw)
60 |
61 | nw = int(iw * resize_scale)
62 | nh = int(ih * resize_scale)
63 |
64 | max_offx = nw - w
65 | max_offy = nh - h
66 |
67 | dx = int(np.random.uniform() * max_offx)
68 | dy = int(np.random.uniform() * max_offy)
69 |
70 | # resize and crop
71 | image = cv2.resize(image, (nw, nh))
72 | image_data = image[dy : (dy + h), dx : (dx + w), :]
73 |
74 | dx, dy = (-dx, -dy)
75 | else:
76 | # scale down
77 | mul = 1 if np.random.uniform() >= 0.5 else -1
78 |
79 | resize_scale = 1. + mul * scale * np.random.uniform()
80 | resize_scale = min( h*resize_scale/ih, w*resize_scale/iw)
81 |
82 | nw = int(iw * resize_scale)
83 | nh = int(ih * resize_scale)
84 |
85 | max_offx = w - nw
86 | max_offy = h - nh
87 |
88 | dx = int(np.random.uniform() * max_offx)
89 | dy = int(np.random.uniform() * max_offy)
90 |
91 | to_x0, to_y0 = max(0, dx), max(0, dy)
92 | from_x0, from_y0 = max(0, -dx), max(0, -dy)
93 | wx, hy = min(w, dx+nw) - to_x0, min(h, dy+nh) - to_y0
94 |
95 | # place image
96 | image_data = np.zeros((*input_shape,ic), dtype='uint8') + 128
97 | image_data[to_y0:to_y0+hy, to_x0:to_x0+wx, :] = cv2.resize(image, (nw, nh))[from_y0:from_y0+hy, from_x0:from_x0+wx, :]
98 |
99 | flip = np.random.uniform() >= 0.5
100 | if flip: image_data = image_data[:,::-1,:]
101 |
102 | # distort color of the image
103 | hue = rand(-hue, hue)
104 | sat = rand(1, sat) if rand()<.5 else 1/rand(1, sat)
105 | val = rand(1, val) if rand()<.5 else 1/rand(1, val)
106 | x = rgb_to_hsv(np.array(image_data)/255.)
107 | x[..., 0] += hue
108 | x[..., 0][x[..., 0]>1] -= 1
109 | x[..., 0][x[..., 0]<0] += 1
110 | x[..., 1] *= sat
111 | x[..., 2] *= val
112 | x[x>1] = 1
113 | x[x<0] = 0
114 | image_data = hsv_to_rgb(x) # numpy array, 0 to 1
115 |
116 | # correct boxes
117 | box_data = np.zeros((max_boxes,5))
118 | if len(box)>0:
119 | np.random.shuffle(box)
120 | box[:, [0,2]] = box[:, [0,2]]*nw/iw + dx
121 | box[:, [1,3]] = box[:, [1,3]]*nh/ih + dy
122 | if flip: box[:, [0,2]] = w - box[:, [2,0]]
123 | box[:, 0:2][box[:, 0:2]<0] = 0
124 | box[:, 2][box[:, 2]>w] = w
125 | box[:, 3][box[:, 3]>h] = h
126 | box_w = box[:, 2] - box[:, 0]
127 | box_h = box[:, 3] - box[:, 1]
128 | box = box[np.logical_and(box_w>1, box_h>1)] # discard invalid box
129 | if len(box)>max_boxes: box = box[:max_boxes]
130 | box_data[:len(box)] = box
131 |
132 | return image_data, box_data
133 |
134 |
135 | def data_generator(annotation_lines, batch_size, input_shape, random):
136 | '''
137 | data generator for fit_generator
138 | '''
139 | n = len(annotation_lines)
140 | i = 0
141 | while True:
142 | image_data = []
143 | box_data = []
144 | for _ in range(batch_size):
145 | image, box = get_random_data(annotation_lines[i], input_shape, max_boxes=50, random=random)
146 | image_data.append(image)
147 | box = box[np.sum(box, axis=1) != 0, :]
148 | box_data.append(box)
149 | i = (i+1) % n
150 | image_data = np.array(image_data)
151 | box_data = np.array(box_data)
152 |
153 | yield image_data, box_data
154 |
155 |
156 | def data_generator_wrapper(annotation_lines, batch_size, input_shape, random):
157 | n = len(annotation_lines)
158 | if n==0 or batch_size<=0: return None
159 | return data_generator(annotation_lines, batch_size, input_shape, random)
--------------------------------------------------------------------------------
/src/utils/fixes.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf
2 |
3 |
4 | def fix_tf_gpu():
5 | '''
6 | Fix for the following error message:
7 | UnknownError: Failed to get convolution algorithm.
8 | This is probably because cuDNN failed to initialize...
9 |
10 | More:
11 | https://www.tensorflow.org/api_docs/python/tf/config/experimental/set_memory_growth
12 | '''
13 |
14 | physical_devices = tf.config.experimental.list_physical_devices('GPU')
15 |
16 | try:
17 | tf.config.experimental.set_memory_growth(physical_devices[0], True)
18 | except:
19 | pass
--------------------------------------------------------------------------------
/src/utils/image.py:
--------------------------------------------------------------------------------
1 | import cv2
2 | import numpy as np
3 | import matplotlib as mpl
4 |
5 |
6 | def letterbox_image(image, size):
7 | '''
8 | Resize image with unchanged aspect ratio using padding
9 | '''
10 |
11 | # original image size
12 | ih, iw, ic = image.shape
13 |
14 | # given size
15 | h, w = size
16 |
17 | # scale and new size of the image
18 | scale = min(w/iw, h/ih)
19 | nw = int(iw*scale)
20 | nh = int(ih*scale)
21 |
22 | # placeholder letter box
23 | new_image = np.zeros((h, w, ic), dtype='uint8') + 128
24 |
25 | # top-left corner
26 | top, left = (h - nh)//2, (w - nw)//2
27 |
28 | # paste the scaled image in the placeholder anchoring at the top-left corner
29 | new_image[top:top+nh, left:left+nw, :] = cv2.resize(image, (nw, nh))
30 |
31 | return new_image
32 |
33 |
34 | def draw_detection(
35 | img,
36 | boxes,
37 | class_names,
38 | # drawing configs
39 | font=cv2.FONT_HERSHEY_DUPLEX,
40 | font_scale=0.5,
41 | box_thickness=2,
42 | border=5,
43 | text_color=(255, 255, 255),
44 | text_weight=1
45 | ):
46 | '''
47 | Draw the bounding boxes on the image
48 | '''
49 | # generate some colors for different classes
50 | num_classes = len(class_names) # number of classes
51 | colors = [mpl.colors.hsv_to_rgb((i/num_classes, 1, 1)) * 255 for i in range(num_classes)]
52 |
53 | # draw the detections
54 | for box in boxes:
55 | x1, y1, x2, y2 = box[:4].astype(int)
56 | score = box[-2]
57 | label = int(box[-1])
58 |
59 | clr = colors[label]
60 |
61 | # draw the bounding box
62 | img = cv2.rectangle(img, (x1, y1), (x2, y2), clr, box_thickness)
63 |
64 | # text: