util.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. import collections
  2. import datetime
  3. import hashlib
  4. import json
  5. import logging
  6. import signal
  7. import subprocess as sp
  8. import threading
  9. import time
  10. import traceback
  11. from abc import ABC, abstractmethod
  12. from multiprocessing import shared_memory
  13. from typing import AnyStr
  14. import cv2
  15. import matplotlib.pyplot as plt
  16. import numpy as np
  17. logger = logging.getLogger(__name__)
  18. def draw_box_with_label(frame, x_min, y_min, x_max, y_max, label, info, thickness=2, color=None, position='ul'):
  19. if color is None:
  20. color = (0,0,255)
  21. display_text = "{}: {}".format(label, info)
  22. cv2.rectangle(frame, (x_min, y_min), (x_max, y_max), color, thickness)
  23. font_scale = 0.5
  24. font = cv2.FONT_HERSHEY_SIMPLEX
  25. # get the width and height of the text box
  26. size = cv2.getTextSize(display_text, font, fontScale=font_scale, thickness=2)
  27. text_width = size[0][0]
  28. text_height = size[0][1]
  29. line_height = text_height + size[1]
  30. # set the text start position
  31. if position == 'ul':
  32. text_offset_x = x_min
  33. text_offset_y = 0 if y_min < line_height else y_min - (line_height+8)
  34. elif position == 'ur':
  35. text_offset_x = x_max - (text_width+8)
  36. text_offset_y = 0 if y_min < line_height else y_min - (line_height+8)
  37. elif position == 'bl':
  38. text_offset_x = x_min
  39. text_offset_y = y_max
  40. elif position == 'br':
  41. text_offset_x = x_max - (text_width+8)
  42. text_offset_y = y_max
  43. # make the coords of the box with a small padding of two pixels
  44. textbox_coords = ((text_offset_x, text_offset_y), (text_offset_x + text_width + 2, text_offset_y + line_height))
  45. cv2.rectangle(frame, textbox_coords[0], textbox_coords[1], color, cv2.FILLED)
  46. cv2.putText(frame, display_text, (text_offset_x, text_offset_y + line_height - 3), font, fontScale=font_scale, color=(0, 0, 0), thickness=2)
  47. def calculate_region(frame_shape, xmin, ymin, xmax, ymax, multiplier=2):
  48. # size is the longest edge and divisible by 4
  49. size = int(max(xmax-xmin, ymax-ymin)//4*4*multiplier)
  50. # dont go any smaller than 300
  51. if size < 300:
  52. size = 300
  53. # x_offset is midpoint of bounding box minus half the size
  54. x_offset = int((xmax-xmin)/2.0+xmin-size/2.0)
  55. # if outside the image
  56. if x_offset < 0:
  57. x_offset = 0
  58. elif x_offset > (frame_shape[1]-size):
  59. x_offset = max(0, (frame_shape[1]-size))
  60. # y_offset is midpoint of bounding box minus half the size
  61. y_offset = int((ymax-ymin)/2.0+ymin-size/2.0)
  62. # # if outside the image
  63. if y_offset < 0:
  64. y_offset = 0
  65. elif y_offset > (frame_shape[0]-size):
  66. y_offset = max(0, (frame_shape[0]-size))
  67. return (x_offset, y_offset, x_offset+size, y_offset+size)
  68. def get_yuv_crop(frame_shape, crop):
  69. # crop should be (x1,y1,x2,y2)
  70. frame_height = frame_shape[0]//3*2
  71. frame_width = frame_shape[1]
  72. # compute the width/height of the uv channels
  73. uv_width = frame_width//2 # width of the uv channels
  74. uv_height = frame_height//4 # height of the uv channels
  75. # compute the offset for upper left corner of the uv channels
  76. uv_x_offset = crop[0]//2 # x offset of the uv channels
  77. uv_y_offset = crop[1]//4 # y offset of the uv channels
  78. # compute the width/height of the uv crops
  79. uv_crop_width = (crop[2] - crop[0])//2 # width of the cropped uv channels
  80. uv_crop_height = (crop[3] - crop[1])//4 # height of the cropped uv channels
  81. # ensure crop dimensions are multiples of 2 and 4
  82. y = (
  83. crop[0],
  84. crop[1],
  85. crop[0] + uv_crop_width*2,
  86. crop[1] + uv_crop_height*4
  87. )
  88. u1 = (
  89. 0 + uv_x_offset,
  90. frame_height + uv_y_offset,
  91. 0 + uv_x_offset + uv_crop_width,
  92. frame_height + uv_y_offset + uv_crop_height
  93. )
  94. u2 = (
  95. uv_width + uv_x_offset,
  96. frame_height + uv_y_offset,
  97. uv_width + uv_x_offset + uv_crop_width,
  98. frame_height + uv_y_offset + uv_crop_height
  99. )
  100. v1 = (
  101. 0 + uv_x_offset,
  102. frame_height + uv_height + uv_y_offset,
  103. 0 + uv_x_offset + uv_crop_width,
  104. frame_height + uv_height + uv_y_offset + uv_crop_height
  105. )
  106. v2 = (
  107. uv_width + uv_x_offset,
  108. frame_height + uv_height + uv_y_offset,
  109. uv_width + uv_x_offset + uv_crop_width,
  110. frame_height + uv_height + uv_y_offset + uv_crop_height
  111. )
  112. return y, u1, u2, v1, v2
  113. def yuv_region_2_rgb(frame, region):
  114. try:
  115. height = frame.shape[0]//3*2
  116. width = frame.shape[1]
  117. # get the crop box if the region extends beyond the frame
  118. crop_x1 = max(0, region[0])
  119. crop_y1 = max(0, region[1])
  120. # ensure these are a multiple of 4
  121. crop_x2 = min(width, region[2])
  122. crop_y2 = min(height, region[3])
  123. crop_box = (crop_x1, crop_y1, crop_x2, crop_y2)
  124. y, u1, u2, v1, v2 = get_yuv_crop(frame.shape, crop_box)
  125. # if the region starts outside the frame, indent the start point in the cropped frame
  126. y_channel_x_offset = abs(min(0, region[0]))
  127. y_channel_y_offset = abs(min(0, region[1]))
  128. uv_channel_x_offset = y_channel_x_offset//2
  129. uv_channel_y_offset = y_channel_y_offset//4
  130. # create the yuv region frame
  131. # make sure the size is a multiple of 4
  132. size = (region[3] - region[1])//4*4
  133. yuv_cropped_frame = np.zeros((size+size//2, size), np.uint8)
  134. # fill in black
  135. yuv_cropped_frame[:] = 128
  136. yuv_cropped_frame[0:size,0:size] = 16
  137. # copy the y channel
  138. yuv_cropped_frame[
  139. y_channel_y_offset:y_channel_y_offset + y[3] - y[1],
  140. y_channel_x_offset:y_channel_x_offset + y[2] - y[0]
  141. ] = frame[
  142. y[1]:y[3],
  143. y[0]:y[2]
  144. ]
  145. uv_crop_width = u1[2] - u1[0]
  146. uv_crop_height = u1[3] - u1[1]
  147. # copy u1
  148. yuv_cropped_frame[
  149. size + uv_channel_y_offset:size + uv_channel_y_offset + uv_crop_height,
  150. 0 + uv_channel_x_offset:0 + uv_channel_x_offset + uv_crop_width
  151. ] = frame[
  152. u1[1]:u1[3],
  153. u1[0]:u1[2]
  154. ]
  155. # copy u2
  156. yuv_cropped_frame[
  157. size + uv_channel_y_offset:size + uv_channel_y_offset + uv_crop_height,
  158. size//2 + uv_channel_x_offset:size//2 + uv_channel_x_offset + uv_crop_width
  159. ] = frame[
  160. u2[1]:u2[3],
  161. u2[0]:u2[2]
  162. ]
  163. # copy v1
  164. yuv_cropped_frame[
  165. size+size//4 + uv_channel_y_offset:size+size//4 + uv_channel_y_offset + uv_crop_height,
  166. 0 + uv_channel_x_offset:0 + uv_channel_x_offset + uv_crop_width
  167. ] = frame[
  168. v1[1]:v1[3],
  169. v1[0]:v1[2]
  170. ]
  171. # copy v2
  172. yuv_cropped_frame[
  173. size+size//4 + uv_channel_y_offset:size+size//4 + uv_channel_y_offset + uv_crop_height,
  174. size//2 + uv_channel_x_offset:size//2 + uv_channel_x_offset + uv_crop_width
  175. ] = frame[
  176. v2[1]:v2[3],
  177. v2[0]:v2[2]
  178. ]
  179. return cv2.cvtColor(yuv_cropped_frame, cv2.COLOR_YUV2RGB_I420)
  180. except:
  181. print(f"frame.shape: {frame.shape}")
  182. print(f"region: {region}")
  183. raise
  184. def intersection(box_a, box_b):
  185. return (
  186. max(box_a[0], box_b[0]),
  187. max(box_a[1], box_b[1]),
  188. min(box_a[2], box_b[2]),
  189. min(box_a[3], box_b[3])
  190. )
  191. def area(box):
  192. return (box[2]-box[0] + 1)*(box[3]-box[1] + 1)
  193. def intersection_over_union(box_a, box_b):
  194. # determine the (x, y)-coordinates of the intersection rectangle
  195. intersect = intersection(box_a, box_b)
  196. # compute the area of intersection rectangle
  197. inter_area = max(0, intersect[2] - intersect[0] + 1) * max(0, intersect[3] - intersect[1] + 1)
  198. if inter_area == 0:
  199. return 0.0
  200. # compute the area of both the prediction and ground-truth
  201. # rectangles
  202. box_a_area = (box_a[2] - box_a[0] + 1) * (box_a[3] - box_a[1] + 1)
  203. box_b_area = (box_b[2] - box_b[0] + 1) * (box_b[3] - box_b[1] + 1)
  204. # compute the intersection over union by taking the intersection
  205. # area and dividing it by the sum of prediction + ground-truth
  206. # areas - the interesection area
  207. iou = inter_area / float(box_a_area + box_b_area - inter_area)
  208. # return the intersection over union value
  209. return iou
  210. def clipped(obj, frame_shape):
  211. # if the object is within 5 pixels of the region border, and the region is not on the edge
  212. # consider the object to be clipped
  213. box = obj[2]
  214. region = obj[4]
  215. if ((region[0] > 5 and box[0]-region[0] <= 5) or
  216. (region[1] > 5 and box[1]-region[1] <= 5) or
  217. (frame_shape[1]-region[2] > 5 and region[2]-box[2] <= 5) or
  218. (frame_shape[0]-region[3] > 5 and region[3]-box[3] <= 5)):
  219. return True
  220. else:
  221. return False
  222. class EventsPerSecond:
  223. def __init__(self, max_events=1000):
  224. self._start = None
  225. self._max_events = max_events
  226. self._timestamps = []
  227. def start(self):
  228. self._start = datetime.datetime.now().timestamp()
  229. def update(self):
  230. if self._start is None:
  231. self.start()
  232. self._timestamps.append(datetime.datetime.now().timestamp())
  233. # truncate the list when it goes 100 over the max_size
  234. if len(self._timestamps) > self._max_events+100:
  235. self._timestamps = self._timestamps[(1-self._max_events):]
  236. def eps(self, last_n_seconds=10):
  237. if self._start is None:
  238. self.start()
  239. # compute the (approximate) events in the last n seconds
  240. now = datetime.datetime.now().timestamp()
  241. seconds = min(now-self._start, last_n_seconds)
  242. return len([t for t in self._timestamps if t > (now-last_n_seconds)]) / seconds
  243. def print_stack(sig, frame):
  244. traceback.print_stack(frame)
  245. def listen():
  246. signal.signal(signal.SIGUSR1, print_stack)
  247. def create_mask(frame_shape, mask):
  248. mask_img = np.zeros(frame_shape, np.uint8)
  249. mask_img[:] = 255
  250. if isinstance(mask, list):
  251. for m in mask:
  252. add_mask(m, mask_img)
  253. elif isinstance(mask, str):
  254. add_mask(mask, mask_img)
  255. return mask_img
  256. def add_mask(mask, mask_img):
  257. points = mask.split(',')
  258. contour = np.array([[int(points[i]), int(points[i+1])] for i in range(0, len(points), 2)])
  259. cv2.fillPoly(mask_img, pts=[contour], color=(0))
  260. class FrameManager(ABC):
  261. @abstractmethod
  262. def create(self, name, size) -> AnyStr:
  263. pass
  264. @abstractmethod
  265. def get(self, name, timeout_ms=0):
  266. pass
  267. @abstractmethod
  268. def close(self, name):
  269. pass
  270. @abstractmethod
  271. def delete(self, name):
  272. pass
  273. class DictFrameManager(FrameManager):
  274. def __init__(self):
  275. self.frames = {}
  276. def create(self, name, size) -> AnyStr:
  277. mem = bytearray(size)
  278. self.frames[name] = mem
  279. return mem
  280. def get(self, name, shape):
  281. mem = self.frames[name]
  282. return np.ndarray(shape, dtype=np.uint8, buffer=mem)
  283. def close(self, name):
  284. pass
  285. def delete(self, name):
  286. del self.frames[name]
  287. class SharedMemoryFrameManager(FrameManager):
  288. def __init__(self):
  289. self.shm_store = {}
  290. def create(self, name, size) -> AnyStr:
  291. shm = shared_memory.SharedMemory(name=name, create=True, size=size)
  292. self.shm_store[name] = shm
  293. return shm.buf
  294. def get(self, name, shape):
  295. if name in self.shm_store:
  296. shm = self.shm_store[name]
  297. else:
  298. shm = shared_memory.SharedMemory(name=name)
  299. self.shm_store[name] = shm
  300. return np.ndarray(shape, dtype=np.uint8, buffer=shm.buf)
  301. def close(self, name):
  302. if name in self.shm_store:
  303. self.shm_store[name].close()
  304. del self.shm_store[name]
  305. def delete(self, name):
  306. if name in self.shm_store:
  307. self.shm_store[name].close()
  308. self.shm_store[name].unlink()
  309. del self.shm_store[name]