├── yolov4 ├── __init__.py ├── helpers.py └── darknet.py ├── misc └── sample1.png ├── setup.py ├── README.md └── test.py /yolov4/__init__.py: -------------------------------------------------------------------------------- 1 | from yolov4.darknet import Detector, MultiGPU -------------------------------------------------------------------------------- /misc/sample1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipperemy/python-darknet-yolo-v4/HEAD/misc/sample1.png -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='yolo-v4', 5 | version='0.5', 6 | author='Philippe Remy', 7 | description='Interface Darknet YOLOv4 with python', 8 | include_package_data=True, 9 | data_files=[ 10 | ], 11 | install_requires=[ 12 | 'numpy', 13 | 'opencv-python', 14 | 'scikit-image', 15 | 'attrs', 16 | 'Pillow' 17 | ], 18 | packages=find_packages(), 19 | ) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## YOLOv4 in Python 2 | 3 | Python interface to Darknet Yolo V4. The multi GPU is supported (load balancer). 4 | 5 | ### Installation 6 | 7 | Compile the Darknet framework first. 8 | 9 | ```bash 10 | sudo apt-get update 11 | sudo apt-get install -y pkg-config git build-essential libopencv-dev wget cmake 12 | git clone https://github.com/AlexeyAB/darknet.git 13 | cd darknet 14 | make LIBSO=1 OPENCV=1 GPU=1 AVX=1 OPENMP=1 CUDNN=1 CUDNN_HALF=1 OPENMP=1 -j $(nproc) 15 | chmod +x darknet 16 | ``` 17 | 18 | Then, download the weights by following the instructions here: https://github.com/AlexeyAB/darknet. 19 | 20 | From there, create a virtual environment with python3.6+ and run this command: 21 | 22 | ```bash 23 | pip install yolo-v4 24 | ``` 25 | 26 | ### Run inference on images 27 | 28 | To run inference on the GPU on an image `data/dog.jpg`, run this script: 29 | 30 | ```python 31 | import numpy as np 32 | from PIL import Image 33 | 34 | from yolov4 import Detector 35 | 36 | img = Image.open('data/dog.jpg') 37 | d = Detector(gpu_id=0) 38 | img_arr = np.array(img.resize((d.network_width(), d.network_height()))) 39 | detections = d.perform_detect(image_path_or_buf=img_arr, show_image=False) 40 | for detection in detections: 41 | box = detection.left_x, detection.top_y, detection.width, detection.height 42 | print(f'{detection.class_name.ljust(10)} | {detection.class_confidence * 100:.1f} % | {box}') 43 | ``` 44 | 45 | ```c 46 | dog | 97.6 % | (100, 236, 147, 334) 47 | truck | 93.0 % | (367, 81, 175, 98) 48 | bicycle | 92.0 % | (90, 134, 362, 315) 49 | pottedplant | 34.1 % | (538, 115, 29, 47) 50 | ``` 51 | 52 |

53 | 54 |

55 | 56 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | from time import time 3 | 4 | import cv2 5 | import numpy as np 6 | from PIL import Image 7 | from tqdm import tqdm 8 | 9 | from yolov4 import Detector, MultiGPU 10 | 11 | c = 0 12 | 13 | 14 | def target(g, desc): 15 | global c 16 | for _ in range(1000): 17 | # print(desc) 18 | g.perform_detect(show_image=False) 19 | if c % 100 == 0: 20 | print(time(), c) 21 | c += 1 22 | print(time(), c) 23 | 24 | 25 | def batch_single(): 26 | img_samples = ['data/person.jpg', 'data/dog.jpg', 'data/person.jpg', 27 | 'data/person.jpg', 'data/dog.jpg', 'data/person.jpg'] 28 | bs = len(img_samples) 29 | image_list = [cv2.imread(k) for k in img_samples] 30 | img_list = [] 31 | d = Detector(gpu_id=0, lib_darknet_path='lib/libdarknet.so', batch_size=bs) 32 | for custom_image_bgr in image_list: 33 | custom_image = cv2.cvtColor(custom_image_bgr, cv2.COLOR_BGR2RGB) 34 | custom_image = cv2.resize( 35 | custom_image, (d.network_width(), d.network_height()), interpolation=cv2.INTER_NEAREST) 36 | custom_image = custom_image.transpose(2, 0, 1) 37 | img_list.append(custom_image) 38 | for _ in tqdm(range(900)): 39 | d.perform_batch_detect(img_list, batch_size=bs) 40 | 41 | 42 | def main_single(): 43 | img = Image.open('data/dog.jpg') 44 | d = Detector(gpu_id=0, lib_darknet_path='lib/libdarknet.so') 45 | img_arr = np.array(img.resize((d.network_width(), d.network_height()))) 46 | for _ in tqdm(range(900)): 47 | d.perform_detect(image_path_or_buf=img_arr, show_image=False) 48 | 49 | 50 | def main(): 51 | g = MultiGPU([ 52 | Detector(gpu_id=0, lib_darknet_path='lib/libdarknet.so'), 53 | Detector(gpu_id=1, lib_darknet_path='lib/libdarknet.so') 54 | ]) 55 | 56 | thread1 = Thread(target=target, args=(g, 't1')) 57 | thread2 = Thread(target=target, args=(g, 't2')) 58 | 59 | thread1.start() 60 | thread2.start() 61 | 62 | thread1.join() 63 | thread2.join() 64 | 65 | 66 | if __name__ == '__main__': 67 | batch_single() 68 | -------------------------------------------------------------------------------- /yolov4/helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | from ctypes import * 3 | from pathlib import Path 4 | 5 | import attr 6 | import numpy as np 7 | 8 | 9 | @attr.s 10 | class DarkNetPredictionResult: 11 | class_name = attr.ib(type=str) # name of the class detected. E.g. dog. 12 | class_confidence = attr.ib(type=float) # probability of the class. E.g. 95%. 13 | left_x = attr.ib(type=int) # box center x. 14 | top_y = attr.ib(type=int) # box center y. 15 | width = attr.ib(type=int) # box width. 16 | height = attr.ib(type=int) # box height. 17 | info = attr.ib(type=dict, default={}) # extra info. Can be blank. 18 | 19 | 20 | def c_array(ctype, values): 21 | arr = (ctype * len(values))() 22 | arr[:] = values 23 | return arr 24 | 25 | 26 | class BOX(Structure): 27 | _fields_ = [("x", c_float), 28 | ("y", c_float), 29 | ("w", c_float), 30 | ("h", c_float)] 31 | 32 | 33 | class DETECTION(Structure): 34 | _fields_ = [("bbox", BOX), 35 | ("classes", c_int), 36 | ("prob", POINTER(c_float)), 37 | ("mask", POINTER(c_float)), 38 | ("objectness", c_float), 39 | ("sort_class", c_int), 40 | ("uc", POINTER(c_float)), 41 | ("points", c_int), 42 | ("embeddings", POINTER(c_float)), 43 | ("embedding_size", c_int), 44 | ("sim", c_float), 45 | ("track_id", c_int)] 46 | 47 | 48 | class DETNUMPAIR(Structure): 49 | _fields_ = [("num", c_int), 50 | ("dets", POINTER(DETECTION))] 51 | 52 | 53 | class IMAGE(Structure): 54 | _fields_ = [("w", c_int), 55 | ("h", c_int), 56 | ("c", c_int), 57 | ("data", POINTER(c_float))] 58 | 59 | 60 | class METADATA(Structure): 61 | _fields_ = [("classes", c_int), 62 | ("names", POINTER(c_char_p))] 63 | 64 | 65 | def array_to_image(arr): 66 | # need to return old values to avoid python freeing memory 67 | arr = arr.transpose(2, 0, 1) 68 | c = arr.shape[0] 69 | h = arr.shape[1] 70 | w = arr.shape[2] 71 | arr = np.ascontiguousarray(arr.flat, dtype=np.float32) / 255.0 72 | data = arr.ctypes.data_as(POINTER(c_float)) 73 | im = IMAGE(w, h, c, data) 74 | return im, arr 75 | 76 | 77 | def init_lib(lib_darknet_path): 78 | # lib = CDLL("/home/pjreddie/documents/darknet/libdarknet.so", RTLD_GLOBAL) 79 | # lib = CDLL("libdarknet.so", RTLD_GLOBAL) 80 | hasGPU = True 81 | if os.name == "nt": 82 | cwd = os.path.dirname(__file__) 83 | os.environ['PATH'] = cwd + ';' + os.environ['PATH'] 84 | winGPUdll = os.path.join(cwd, "yolo_cpp_dll.dll") 85 | winNoGPUdll = os.path.join(cwd, "yolo_cpp_dll_nogpu.dll") 86 | envKeys = list() 87 | for k, v in os.environ.items(): 88 | envKeys.append(k) 89 | try: 90 | try: 91 | tmp = os.environ["FORCE_CPU"].lower() 92 | if tmp in ["1", "true", "yes", "on"]: 93 | raise ValueError("ForceCPU") 94 | else: 95 | print("Flag value '" + tmp + "' not forcing CPU mode") 96 | except KeyError: 97 | # We never set the flag 98 | if 'CUDA_VISIBLE_DEVICES' in envKeys: 99 | if int(os.environ['CUDA_VISIBLE_DEVICES']) < 0: 100 | raise ValueError("ForceCPU") 101 | try: 102 | global DARKNET_FORCE_CPU 103 | if DARKNET_FORCE_CPU: 104 | raise ValueError("ForceCPU") 105 | except NameError: 106 | pass 107 | # print(os.environ.keys()) 108 | # print("FORCE_CPU flag undefined, proceeding with GPU") 109 | if not os.path.exists(winGPUdll): 110 | raise ValueError("NoDLL") 111 | lib = CDLL(winGPUdll, RTLD_GLOBAL) 112 | except (KeyError, ValueError): 113 | hasGPU = False 114 | if os.path.exists(winNoGPUdll): 115 | lib = CDLL(winNoGPUdll, RTLD_GLOBAL) 116 | print("Notice: CPU-only mode") 117 | else: 118 | # Try the other way, in case no_gpu was 119 | # compile but not renamed 120 | lib = CDLL(winGPUdll, RTLD_GLOBAL) 121 | print( 122 | "Environment variables indicated a CPU run, but we didn't find `" + winNoGPUdll + "`. Trying a GPU run anyway.") 123 | else: 124 | lib = CDLL(Path(lib_darknet_path).resolve(), RTLD_GLOBAL) 125 | 126 | lib.network_width.argtypes = [c_void_p] 127 | lib.network_width.restype = c_int 128 | lib.network_height.argtypes = [c_void_p] 129 | lib.network_height.restype = c_int 130 | copy_image_from_bytes = lib.copy_image_from_bytes 131 | copy_image_from_bytes.argtypes = [IMAGE, c_char_p] 132 | return lib, hasGPU 133 | 134 | 135 | def read_alt_names(meta_path: str): 136 | try: 137 | with open(meta_path) as metaFH: 138 | metaContents = metaFH.read() 139 | import re 140 | match = re.search("names *= *(.*)$", metaContents, re.IGNORECASE | re.MULTILINE) 141 | if match: 142 | result = match.group(1) 143 | else: 144 | result = None 145 | try: 146 | if os.path.exists(result): 147 | with open(result) as namesFH: 148 | namesList = namesFH.read().strip().split("\n") 149 | altNames = [x.strip() for x in namesList] 150 | return altNames 151 | except TypeError: 152 | pass 153 | except Exception: 154 | pass 155 | return None 156 | -------------------------------------------------------------------------------- /yolov4/darknet.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | from concurrent.futures.thread import ThreadPoolExecutor 4 | from ctypes import * 5 | from pathlib import Path 6 | from threading import Lock 7 | from typing import List 8 | 9 | import numpy as np 10 | 11 | from yolov4.helpers import init_lib, DETECTION, DETNUMPAIR, METADATA, IMAGE, read_alt_names, DarkNetPredictionResult 12 | 13 | 14 | class Detector: 15 | 16 | def __init__( 17 | self, 18 | config_path='cfg/yolov4.cfg', 19 | weights_path='yolov4.weights', 20 | meta_path='cfg/coco.data', 21 | lib_darknet_path='libdarknet.so', 22 | batch_size=1, 23 | gpu_id=None 24 | ): 25 | """ 26 | :param config_path: Path to the configuration file. Raises ValueError if not found. 27 | :param weights_path: Path to the weights file. Raises ValueError if not found. 28 | :param meta_path: Path to the data file. Raises ValueError if not found. 29 | :param lib_darknet_path: Path to the darknet library (.so in linux). 30 | :param gpu_id: GPU on which to perform the inference. 31 | """ 32 | self.config_path = config_path 33 | self.weights_path = weights_path 34 | self.meta_path = meta_path 35 | self.gpu_id = gpu_id 36 | # to make sure we have only one inference per GPU. 37 | self.lock = Lock() 38 | 39 | self.net_main = None 40 | self.meta_main = None 41 | self.alt_names = None 42 | 43 | self.lib, self.has_gpu = init_lib(lib_darknet_path) 44 | 45 | self.predict = self.lib.network_predict_ptr 46 | self.predict.argtypes = [c_void_p, POINTER(c_float)] 47 | self.predict.restype = POINTER(c_float) 48 | 49 | # definition of all the bindings (functions to interface with C). 50 | if self.has_gpu: 51 | self.set_gpu = self.lib.cuda_set_device 52 | self.set_gpu.argtypes = [c_int] 53 | 54 | self.init_cpu = self.lib.init_cpu 55 | 56 | self.make_image = self.lib.make_image 57 | self.make_image.argtypes = [c_int, c_int, c_int] 58 | self.make_image.restype = IMAGE 59 | 60 | self.get_network_boxes = self.lib.get_network_boxes 61 | self.get_network_boxes.argtypes = [c_void_p, c_int, c_int, c_float, c_float, POINTER(c_int), c_int, 62 | POINTER(c_int), 63 | c_int] 64 | self.get_network_boxes.restype = POINTER(DETECTION) 65 | 66 | self.make_network_boxes = self.lib.make_network_boxes 67 | self.make_network_boxes.argtypes = [c_void_p] 68 | self.make_network_boxes.restype = POINTER(DETECTION) 69 | 70 | self.free_detections = self.lib.free_detections 71 | self.free_detections.argtypes = [POINTER(DETECTION), c_int] 72 | 73 | self.free_batch_detections = self.lib.free_batch_detections 74 | self.free_batch_detections.argtypes = [POINTER(DETNUMPAIR), c_int] 75 | 76 | self.free_ptrs = self.lib.free_ptrs 77 | self.free_ptrs.argtypes = [POINTER(c_void_p), c_int] 78 | 79 | self.network_predict = self.lib.network_predict_ptr 80 | self.network_predict.argtypes = [c_void_p, POINTER(c_float)] 81 | 82 | self.reset_rnn = self.lib.reset_rnn 83 | self.reset_rnn.argtypes = [c_void_p] 84 | 85 | self.load_net = self.lib.load_network 86 | self.load_net.argtypes = [c_char_p, c_char_p, c_int] 87 | self.load_net.restype = c_void_p 88 | 89 | self.load_net_custom = self.lib.load_network_custom 90 | self.load_net_custom.argtypes = [c_char_p, c_char_p, c_int, c_int] 91 | self.load_net_custom.restype = c_void_p 92 | 93 | self.do_nms_obj = self.lib.do_nms_obj 94 | self.do_nms_obj.argtypes = [POINTER(DETECTION), c_int, c_int, c_float] 95 | 96 | self.do_nms_sort = self.lib.do_nms_sort 97 | self.do_nms_sort.argtypes = [POINTER(DETECTION), c_int, c_int, c_float] 98 | 99 | self.free_image = self.lib.free_image 100 | self.free_image.argtypes = [IMAGE] 101 | 102 | self.letterbox_image = self.lib.letterbox_image 103 | self.letterbox_image.argtypes = [IMAGE, c_int, c_int] 104 | self.letterbox_image.restype = IMAGE 105 | 106 | self.load_meta = self.lib.get_metadata 107 | self.lib.get_metadata.argtypes = [c_char_p] 108 | self.lib.get_metadata.restype = METADATA 109 | 110 | self.load_image = self.lib.load_image_color 111 | self.load_image.argtypes = [c_char_p, c_int, c_int] 112 | self.load_image.restype = IMAGE 113 | 114 | self.rgbgr_image = self.lib.rgbgr_image 115 | self.rgbgr_image.argtypes = [IMAGE] 116 | 117 | self.predict_image = self.lib.network_predict_image 118 | self.predict_image.argtypes = [c_void_p, IMAGE] 119 | self.predict_image.restype = POINTER(c_float) 120 | 121 | self.copy_image_from_bytes = self.lib.copy_image_from_bytes 122 | self.copy_image_from_bytes.argtypes = [IMAGE, c_char_p] 123 | 124 | self.predict_image_letterbox = self.lib.network_predict_image_letterbox 125 | self.predict_image_letterbox.argtypes = [c_void_p, IMAGE] 126 | self.predict_image_letterbox.restype = POINTER(c_float) 127 | 128 | self.network_predict_batch = self.lib.network_predict_batch 129 | self.network_predict_batch.argtypes = [c_void_p, IMAGE, c_int, c_int, c_int, 130 | c_float, c_float, POINTER(c_int), c_int, c_int] 131 | self.network_predict_batch.restype = POINTER(DETNUMPAIR) 132 | 133 | if not Path(self.config_path).exists(): 134 | raise ValueError('Invalid config path `' + os.path.abspath(self.config_path) + '`') 135 | if not Path(self.weights_path).exists(): 136 | raise ValueError('Invalid weight path `' + os.path.abspath(self.weights_path) + '`') 137 | if not Path(meta_path).exists(): 138 | raise ValueError('Invalid data file path `' + os.path.abspath(self.meta_path) + '`') 139 | if self.gpu_id is not None: 140 | print(f'GPU -> {self.gpu_id}.') 141 | self.set_gpu(self.gpu_id) 142 | # batch size = 1 143 | self.net_main = self.load_net_custom(self.config_path.encode('ascii'), self.weights_path.encode('ascii'), 144 | 0, batch_size) 145 | self.meta_main = self.load_meta(self.meta_path.encode('ascii')) 146 | # In Python 3, the metafile default access craps out on Windows (but not Linux) 147 | # Read the names file and create a list to feed to detect 148 | self.alt_names = read_alt_names(self.meta_path) 149 | 150 | self.darknet_image = self.make_image(self.network_width(), self.network_height(), 3) 151 | 152 | def network_width(self): 153 | net = self.net_main 154 | return self.lib.network_width(net) 155 | 156 | def network_height(self): 157 | net = self.net_main 158 | return self.lib.network_height(net) 159 | 160 | def classify(self, im): 161 | net, meta = self.net_main, self.meta_main 162 | out = self.predict_image(net, im) 163 | res = [] 164 | for i in range(meta.classes): 165 | if self.alt_names is None: 166 | nameTag = meta.names[i] 167 | else: 168 | nameTag = self.alt_names[i] 169 | res.append((nameTag, out[i])) 170 | res = sorted(res, key=lambda x: -x[1]) 171 | return res 172 | 173 | def detect(self, image, thresh=.5, hier_thresh=.5, nms=.45, debug=False): 174 | # 175 | # custom_image_bgr = cv2.imread(image.decode('utf8')) # use: detect(,,imagePath,) 176 | # custom_image = cv2.cvtColor(custom_image_bgr, cv2.COLOR_BGR2RGB) 177 | # custom_image = cv2.resize(custom_image, (self.network_width(), self.network_height()), 178 | # interpolation=cv2.INTER_LINEAR) 179 | # img = Image.open(image.decode('utf8')) 180 | # custom_image2 = np.array(img.resize((self.network_width(), self.network_height()))) 181 | # self.copy_image_from_bytes(self.darknet_image, custom_image2.tobytes()) 182 | # im2 = self.load_image(image, 0, 0) 183 | if isinstance(image, str): 184 | if not os.path.exists(image): 185 | raise ValueError('Invalid image path `' + os.path.abspath(image) + '`') 186 | image = image.encode('ascii') 187 | im = self.load_image(image, 0, 0) 188 | else: 189 | self.copy_image_from_bytes(self.darknet_image, image.tobytes()) 190 | im = self.darknet_image 191 | 192 | ret = self.detect_image(im, thresh, hier_thresh, nms, debug) 193 | if isinstance(image, str): 194 | self.free_image(im) # just when we load a new image. 195 | return ret 196 | 197 | def detect_image(self, im, thresh=.5, hier_thresh=.5, nms=.45, debug=False): 198 | net, meta = self.net_main, self.meta_main 199 | num = c_int(0) 200 | if debug: 201 | print('Assigned num') 202 | pnum = pointer(num) 203 | if debug: 204 | print('Assigned pnum') 205 | self.predict_image(net, im) 206 | letter_box = 0 207 | # predict_image_letterbox(net, im) 208 | # letter_box = 1 209 | if debug: 210 | print('did prediction') 211 | dets = self.get_network_boxes(net, im.w, im.h, thresh, hier_thresh, None, 0, pnum, letter_box) 212 | if debug: 213 | print('Got dets') 214 | num = pnum[0] 215 | if debug: 216 | print('got zeroth index of pnum') 217 | if nms: 218 | self.do_nms_sort(dets, num, meta.classes, nms) 219 | if debug: 220 | print('did sort') 221 | res = [] 222 | if debug: 223 | print('about to range') 224 | for j in range(num): 225 | if debug: 226 | print('Ranging on ' + str(j) + ' of ' + str(num)) 227 | if debug: 228 | print('Classes: ' + str(meta), meta.classes, meta.names) 229 | for i in range(meta.classes): 230 | if debug: 231 | print('Class-ranging on ' + str(i) + ' of ' + str(meta.classes) + '= ' + str(dets[j].prob[i])) 232 | if dets[j].prob[i] > 0: 233 | b = dets[j].bbox 234 | if self.alt_names is None: 235 | nameTag = meta.names[i] 236 | else: 237 | nameTag = self.alt_names[i] 238 | if debug: 239 | print('Got bbox', b) 240 | print(nameTag) 241 | print(dets[j].prob[i]) 242 | print((b.x, b.y, b.w, b.h)) 243 | res.append((nameTag, dets[j].prob[i], (b.x, b.y, b.w, b.h))) 244 | if debug: 245 | print('did range') 246 | res = sorted(res, key=lambda x: -x[1]) 247 | if debug: 248 | print('did sort') 249 | self.free_detections(dets, num) 250 | if debug: 251 | print('freed detections') 252 | return res 253 | 254 | def perform_detect( 255 | self, 256 | image_path_or_buf='data/dog.jpg', 257 | thresh: float = 0.25, 258 | show_image: bool = True, 259 | make_image_only: bool = False, 260 | ): 261 | self.lock.acquire() 262 | assert 0 < thresh < 1, 'Threshold should be a float between zero and one (non-inclusive)' 263 | detections = self.detect(image_path_or_buf, thresh) 264 | if show_image and isinstance(image_path_or_buf, str): 265 | try: 266 | from skimage import io, draw 267 | image = io.imread(image_path_or_buf) 268 | print('*** ' + str(len(detections)) + ' Results, color coded by confidence ***') 269 | imcaption = [] 270 | for detection in detections: 271 | label = detection[0] 272 | confidence = detection[1] 273 | pstring = label + ': ' + str(np.rint(100 * confidence)) + '%' 274 | imcaption.append(pstring) 275 | print(pstring) 276 | bounds = detection[2] 277 | shape = image.shape 278 | # x = shape[1] 279 | # xExtent = int(x * bounds[2] / 100) 280 | # y = shape[0] 281 | # yExtent = int(y * bounds[3] / 100) 282 | yExtent = int(bounds[3]) 283 | xEntent = int(bounds[2]) 284 | # Coordinates are around the center 285 | xCoord = int(bounds[0] - bounds[2] / 2) 286 | yCoord = int(bounds[1] - bounds[3] / 2) 287 | boundingBox = [ 288 | [xCoord, yCoord], 289 | [xCoord, yCoord + yExtent], 290 | [xCoord + xEntent, yCoord + yExtent], 291 | [xCoord + xEntent, yCoord] 292 | ] 293 | # Wiggle it around to make a 3px border 294 | rr, cc = draw.polygon_perimeter([x[1] for x in boundingBox], [x[0] for x in boundingBox], 295 | shape=shape) 296 | rr2, cc2 = draw.polygon_perimeter([x[1] + 1 for x in boundingBox], [x[0] for x in boundingBox], 297 | shape=shape) 298 | rr3, cc3 = draw.polygon_perimeter([x[1] - 1 for x in boundingBox], [x[0] for x in boundingBox], 299 | shape=shape) 300 | rr4, cc4 = draw.polygon_perimeter([x[1] for x in boundingBox], [x[0] + 1 for x in boundingBox], 301 | shape=shape) 302 | rr5, cc5 = draw.polygon_perimeter([x[1] for x in boundingBox], [x[0] - 1 for x in boundingBox], 303 | shape=shape) 304 | boxColor = (int(255 * (1 - (confidence ** 2))), int(255 * (confidence ** 2)), 0) 305 | draw.set_color(image, (rr, cc), boxColor, alpha=0.8) 306 | draw.set_color(image, (rr2, cc2), boxColor, alpha=0.8) 307 | draw.set_color(image, (rr3, cc3), boxColor, alpha=0.8) 308 | draw.set_color(image, (rr4, cc4), boxColor, alpha=0.8) 309 | draw.set_color(image, (rr5, cc5), boxColor, alpha=0.8) 310 | if not make_image_only: 311 | io.imshow(image) 312 | io.show() 313 | detections = { 314 | 'detections': detections, 315 | 'image': image, 316 | 'caption': '\n
'.join(imcaption) 317 | } 318 | except Exception as e: 319 | print('Unable to show image: ' + str(e)) 320 | 321 | results = [] 322 | sub_detections = detections['detections'] if 'detections' in detections else detections 323 | for detection in sub_detections: 324 | class_name, class_confidence, bbox = detection 325 | x, y, w, h = bbox 326 | x_min = int(x - (w / 2)) 327 | y_min = int(y - (h / 2)) 328 | results.append(DarkNetPredictionResult( 329 | class_name=class_name, 330 | class_confidence=class_confidence, 331 | left_x=max(0, int(x_min)), 332 | top_y=max(0, int(y_min)), 333 | width=max(0, int(w)), 334 | height=max(0, int(h)) 335 | )) 336 | self.lock.release() 337 | return results 338 | 339 | def perform_batch_detect(self, image_list, thresh=0.25, hier_thresh=.5, nms=.45, batch_size=3): 340 | net, meta = self.net_main, self.meta_main 341 | # NB! Image sizes should be the same 342 | # You can change the images, yet, be sure that they have the same width and height 343 | pred_height, pred_width, c = image_list[0].shape 344 | net_width, net_height = (self.network_width(), self.network_height()) 345 | arr = np.concatenate(image_list, axis=0) 346 | arr = np.ascontiguousarray(arr.flat, dtype=np.float32) / 255.0 347 | data = arr.ctypes.data_as(POINTER(c_float)) 348 | im = IMAGE(net_width, net_height, c, data) 349 | 350 | batch_dets = self.network_predict_batch(net, im, batch_size, pred_width, 351 | pred_height, thresh, hier_thresh, None, 0, 0) 352 | batch_boxes = [] 353 | batch_scores = [] 354 | batch_classes = [] 355 | for b in range(batch_size): 356 | num = batch_dets[b].num 357 | dets = batch_dets[b].dets 358 | if nms: 359 | self.do_nms_obj(dets, num, meta.classes, nms) 360 | boxes = [] 361 | scores = [] 362 | classes = [] 363 | for i in range(num): 364 | det = dets[i] 365 | score = -1 366 | label = None 367 | for c in range(det.classes): 368 | p = det.prob[c] 369 | if p > score: 370 | score = p 371 | label = c 372 | if score > thresh: 373 | box = det.bbox 374 | left, top, right, bottom = map(int, (box.x - box.w / 2, box.y - box.h / 2, 375 | box.x + box.w / 2, box.y + box.h / 2)) 376 | boxes.append((top, left, bottom, right)) 377 | scores.append(score) 378 | classes.append(label) 379 | # boxColor = (int(255 * (1 - (score ** 2))), int(255 * (score ** 2)), 0) 380 | # cv2.rectangle(image_list[b], (left, top), 381 | # (right, bottom), boxColor, 2) 382 | # cv2.imwrite(os.path.basename(img_samples[b]), image_list[b]) 383 | 384 | batch_boxes.append(boxes) 385 | batch_scores.append(scores) 386 | batch_classes.append(classes) 387 | self.free_batch_detections(batch_dets, batch_size) 388 | # batch_classes: use altNames to fetch the real names. 389 | return batch_boxes, batch_scores, batch_classes 390 | 391 | 392 | class MultiGPU: 393 | 394 | def __init__(self, detectors_list: List[Detector]): 395 | self.detectors = {} 396 | for detector in detectors_list: 397 | gpu_id = detector.gpu_id 398 | if gpu_id is None: 399 | raise ValueError('To use MultiGPU, every gpu_id should be defined.') 400 | self.detectors[gpu_id] = detector 401 | self.num_gpus = len(self.detectors) 402 | self.thread_pool = ThreadPoolExecutor(max_workers=self.num_gpus, thread_name_prefix='detector_') 403 | self.counter = 0 # used to dispatch, assuming the load is same on each GPU. 404 | self.locks = {} 405 | for gpu_id in self.detectors.keys(): 406 | self.locks[gpu_id] = Lock() 407 | 408 | def find_available_gpu(self): 409 | list_to_choose_from = [] 410 | for gpu_id, lock in self.locks.items(): 411 | if not lock.locked(): 412 | list_to_choose_from.append(gpu_id) 413 | if len(list_to_choose_from) == 0: 414 | list_to_choose_from = list(self.locks.keys()) 415 | return random.choice(list_to_choose_from) 416 | 417 | def _perform_detect(self, gpu_id, *args): 418 | return self.detectors[gpu_id].perform_detect(*args) 419 | 420 | def perform_detect(self, *args, **kwargs): 421 | gpu_id = self.find_available_gpu() 422 | lock = self.locks[gpu_id] 423 | with lock: 424 | # print(f'Lock for GPU {gpu_id} acquired.') 425 | 426 | def run(*arg, **kwarg): 427 | return self.detectors[gpu_id].perform_detect(*arg, **kwarg) 428 | 429 | future = self.thread_pool.submit(fn=run, *args, **kwargs) 430 | return future.result() 431 | --------------------------------------------------------------------------------