util.py 12 KB

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