Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

446 rindas
29KB

  1. # -*- coding: utf-8 -*-
  2. from concurrent.futures import ThreadPoolExecutor
  3. from multiprocessing import Process
  4. from os import getpid
  5. from os.path import join
  6. from time import time, sleep
  7. from traceback import format_exc
  8. import cv2
  9. import psutil
  10. from loguru import logger
  11. from enums.ExceptionEnum import ExceptionType
  12. from exception.CustomerException import ServiceException
  13. from util import ImageUtils
  14. from util.Cv2Utils import video_conjuncing, write_or_video, write_ai_video, push_video_stream, close_all_p
  15. from util.ImageUtils import url2Array, add_water_pic
  16. from util.LogUtils import init_log
  17. from util.PlotsUtils import draw_painting_joint, xywh2xyxy2
  18. from util.QueUtil import get_no_block_queue, put_queue, clear_queue
  19. class PushStreamProcess(Process):
  20. __slots__ = ("_msg", "_push_queue", "_image_queue", '_push_ex_queue', '_hb_queue', "_context")
  21. def __init__(self, *args):
  22. super().__init__()
  23. # 传参
  24. self._msg, self._push_queue, self._image_queue, self._push_ex_queue, self._hb_queue, self._context = args
  25. def build_logo_url(self):
  26. logo = None
  27. if self._context["video"]["video_add_water"]:
  28. logo = self._msg.get("logo_url")
  29. if logo:
  30. logo = url2Array(logo, enable_ex=False)
  31. if logo is None:
  32. logo = cv2.imread(join(self._context['base_dir'], "image/logo.png"), -1)
  33. self._context["logo"] = logo
  34. @staticmethod
  35. def handle_image(det_xywh, det, frame_score, copy_frame, draw_config, code_list):
  36. code, det_result = det
  37. # 每个单独模型处理
  38. # 模型编号、100帧的所有问题, 检测目标、颜色、文字图片
  39. if len(det_result) > 0:
  40. font_config, allowedList = draw_config["font_config"], draw_config[code]["allowedList"]
  41. rainbows, label_arrays = draw_config[code]["rainbows"], draw_config[code]["label_arrays"]
  42. for qs in det_result:
  43. box, score, cls = xywh2xyxy2(qs)
  44. if cls not in allowedList or score < frame_score:
  45. continue
  46. label_array, color = label_arrays[cls], rainbows[cls]
  47. draw_painting_joint(box, copy_frame, label_array, score, color, font_config)
  48. if det_xywh.get(code) is None:
  49. det_xywh[code], code_list[code] = {}, {}
  50. cd = det_xywh[code].get(cls)
  51. if cd is None:
  52. code_list[code][cls] = 1
  53. det_xywh[code][cls] = [[cls, box, score, label_array, color]]
  54. else:
  55. code_list[code][cls] += 1
  56. det_xywh[code][cls].append([cls, box, score, label_array, color])
  57. class OnPushStreamProcess(PushStreamProcess):
  58. __slots__ = ()
  59. def run(self):
  60. self.build_logo_url()
  61. msg, context = self._msg, self._context
  62. base_dir, env, orFilePath, aiFilePath, logo, service_timeout, frame_score = context["base_dir"], \
  63. context['env'], context["orFilePath"], context["aiFilePath"], context["logo"], \
  64. int(context["service"]["timeout"]) + 120, context["service"]["filter"]["frame_score"]
  65. request_id, push_url = msg["request_id"], msg["push_url"]
  66. push_queue, image_queue, push_ex_queue, hb_queue = self._push_queue, self._image_queue, self._push_ex_queue, \
  67. self._hb_queue
  68. or_video_file, ai_video_file, push_p, ex = None, None, None, None
  69. ex_status = True
  70. high_score_image = {}
  71. # 相似度, 默认值0.65
  72. similarity = context["service"]["filter"]["similarity"]
  73. # 图片相似度开关
  74. picture_similarity = bool(context["service"]["filter"]["picture_similarity"])
  75. frame_step = int(context["service"]["filter"]["frame_step"])
  76. try:
  77. init_log(base_dir, env)
  78. logger.info("开始实时启动推流进程!requestId:{}", request_id)
  79. with ThreadPoolExecutor(max_workers=2) as t:
  80. # 定义三种推流、写原视频流、写ai视频流策略
  81. # 第一个参数时间, 第二个参数重试次数
  82. p_push_status, or_write_status, ai_write_status = [0, 0], [0, 0], [0, 0]
  83. start_time = time()
  84. while True:
  85. # 检测推流执行超时时间, 1.防止任务运行超时 2.主进程挂了,子进程运行超时
  86. if time() - start_time > service_timeout:
  87. logger.error("推流超时, requestId: {}", request_id)
  88. raise ServiceException(ExceptionType.TASK_EXCUTE_TIMEOUT.value[0],
  89. ExceptionType.TASK_EXCUTE_TIMEOUT.value[1])
  90. # 系统由于各种问题可能会杀死内存使用多的进程, 自己杀掉自己
  91. if psutil.Process(getpid()).ppid() == 1:
  92. logger.info("推流进程检测到父进程异常停止, 自动停止推流进程, requestId: {}", request_id)
  93. ex_status = False
  94. for q in [push_queue, image_queue, push_ex_queue, hb_queue]:
  95. clear_queue(q)
  96. break
  97. # 获取推流的视频帧
  98. push_r = get_no_block_queue(push_queue)
  99. if push_r is not None:
  100. if push_r[0] == 1:
  101. frame_list, frame_index_list, all_frames, draw_config, push_objs = push_r[1]
  102. for i, frame in enumerate(frame_list):
  103. # 复制帧用来画图
  104. copy_frame = frame.copy()
  105. det_xywh, thread_p = {}, []
  106. for det in push_objs[i]:
  107. code, det_result = det
  108. # 每个单独模型处理
  109. # 模型编号、100帧的所有问题, 检测目标、颜色、文字图片
  110. if len(det_result) > 0:
  111. font_config, allowedList = draw_config["font_config"], draw_config[code]["allowedList"]
  112. rainbows, label_arrays = draw_config[code]["rainbows"], draw_config[code]["label_arrays"]
  113. for qs in det_result:
  114. box, score, cls = xywh2xyxy2(qs)
  115. if cls not in allowedList or score < frame_score:
  116. continue
  117. label_array, color = label_arrays[cls], rainbows[cls]
  118. rr = t.submit(draw_painting_joint, box, copy_frame, label_array, score, color, font_config)
  119. thread_p.append(rr)
  120. if det_xywh.get(code) is None:
  121. det_xywh[code] = {}
  122. cd = det_xywh[code].get(cls)
  123. if cd is None:
  124. det_xywh[code][cls] = [[cls, box, score, label_array, color]]
  125. else:
  126. det_xywh[code][cls].append([cls, box, score, label_array, color])
  127. if logo:
  128. frame = add_water_pic(frame, logo, request_id)
  129. copy_frame = add_water_pic(copy_frame, logo, request_id)
  130. if len(thread_p) > 0:
  131. for r in thread_p:
  132. r.result()
  133. frame_merge = video_conjuncing(frame, copy_frame)
  134. # 写原视频到本地
  135. write_or_video_result = t.submit(write_or_video, frame, orFilePath, or_video_file,
  136. or_write_status, request_id)
  137. # 写识别视频到本地
  138. write_ai_video_result = t.submit(write_ai_video, frame_merge, aiFilePath,
  139. ai_video_file, ai_write_status, request_id)
  140. push_stream_result = t.submit(push_video_stream, frame_merge, push_p, push_url,
  141. p_push_status, request_id)
  142. # 指定多少帧上传一次问题
  143. if high_score_image.get("current_frame") is None:
  144. high_score_image["current_frame"] = frame_index_list[i]
  145. diff_frame_num = frame_index_list[i] - high_score_image["current_frame"]
  146. # 指定帧内处理逻辑
  147. if frame_step >= diff_frame_num and high_score_image.get("code") is not None and len(det_xywh) > 0:
  148. # 所有的模型编号
  149. cache_codes = set(high_score_image["code"].keys())
  150. # 遍历当前模型
  151. for det_code, det_clss in det_xywh.items():
  152. # 如果模型编号不在缓存中
  153. if det_code not in cache_codes:
  154. high_score_image["code"][det_code] = {}
  155. for cls, cls_list in det_xywh[det_code].items():
  156. if len(cls_list) > 0:
  157. high_score_image["code"][det_code][cls] = (frame, frame_index_list[i], cls_list)
  158. else:
  159. # 如果模型编号在缓存中, 检查检测目标的情况
  160. cache_clss = set(high_score_image["code"][det_code].keys())
  161. for cls, cls_list in det_xywh[det_code].items():
  162. if len(cls_list) > 0:
  163. if cls not in cache_clss:
  164. high_score_image["code"][det_code][cls] = (frame, frame_index_list[i], cls_list)
  165. else:
  166. if len(det_xywh[det_code][cls]) > len(high_score_image["code"][det_code][cls][2]):
  167. high_score_image["code"][det_code][cls] = (frame, frame_index_list[i], cls_list)
  168. elif len(det_xywh[det_code][cls]) == len(high_score_image["code"][det_code][cls][2]):
  169. # [cls, box, score, label_array, color]
  170. hs = 0
  171. for ii, s in enumerate(cls_list):
  172. if s[2] >= high_score_image["code"][det_code][cls][2][ii][2]:
  173. hs += 1
  174. if hs == len(cls_list):
  175. high_score_image["code"][det_code][cls] = (frame, frame_index_list[i], cls_list)
  176. if diff_frame_num > frame_step:
  177. if high_score_image.get("code") is not None:
  178. high_score_image["or_frame"] = frame
  179. put_queue(image_queue, (1, [high_score_image["code"], all_frames, draw_config["font_config"]]))
  180. high_score_image["code"] = None
  181. high_score_image["current_frame"] = None
  182. # 如果有问题, 走下面的逻辑
  183. if len(det_xywh) > 0:
  184. flag = True
  185. # 比较上一次识别到问题的帧和当前问题帧的相似度
  186. if picture_similarity and high_score_image.get("or_frame") is not None:
  187. hash1 = ImageUtils.dHash(high_score_image["or_frame"])
  188. hash2 = ImageUtils.dHash(frame)
  189. dist = ImageUtils.Hamming_distance(hash1, hash2)
  190. similarity_1 = 1 - dist * 1.0 / 64
  191. if similarity_1 >= similarity:
  192. flag = False
  193. # 如果缓存问题是空的,给缓存添加问题
  194. if flag and high_score_image.get("code") is None:
  195. high_score_image["code"] = {}
  196. for code, det_list in det_xywh.items():
  197. if high_score_image["code"].get(code) is None:
  198. high_score_image["code"][code] = {}
  199. for cls, cls_list in det_list.items():
  200. high_score_image["code"][code][cls] = (frame, frame_index_list[i], cls_list)
  201. push_p = push_stream_result.result(timeout=5)
  202. ai_video_file = write_ai_video_result.result(timeout=5)
  203. or_video_file = write_or_video_result.result(timeout=5)
  204. # 接收停止指令
  205. if push_r[0] == 2:
  206. if 'stop' == push_r[1]:
  207. logger.info("停止推流进程, requestId: {}", request_id)
  208. break
  209. if 'stop_ex' == push_r[1]:
  210. ex_status = False
  211. logger.info("停止推流进程, requestId: {}", request_id)
  212. break
  213. del push_r
  214. else:
  215. sleep(1)
  216. except ServiceException as s:
  217. logger.error("推流进程异常:{}, requestId:{}", s.msg, request_id)
  218. ex = s.code, s.msg
  219. except Exception:
  220. logger.error("推流进程异常:{}, requestId:{}", format_exc(), request_id)
  221. ex = ExceptionType.SERVICE_INNER_EXCEPTION.value[0], ExceptionType.SERVICE_INNER_EXCEPTION.value[1]
  222. finally:
  223. # 关闭推流管, 原视频写对象, 分析视频写对象
  224. close_all_p(push_p, or_video_file, ai_video_file, request_id)
  225. if ex:
  226. code, msg = ex
  227. put_queue(push_ex_queue, (1, code, msg), timeout=2)
  228. else:
  229. if ex_status:
  230. # 关闭推流的时候, 等待1分钟图片队列处理完,如果1分钟内没有处理完, 清空图片队列, 丢弃没有上传的图片
  231. c_time = time()
  232. while time() - c_time < 60:
  233. if image_queue.qsize() == 0 or image_queue.empty():
  234. break
  235. sleep(2)
  236. for q in [push_queue, image_queue, push_ex_queue, hb_queue]:
  237. clear_queue(q)
  238. logger.info("推流进程停止完成!图片队列大小: {}, requestId:{}", image_queue.qsize(), request_id)
  239. class OffPushStreamProcess(PushStreamProcess):
  240. __slots__ = ()
  241. def run(self):
  242. self.build_logo_url()
  243. msg, context = self._msg, self._context
  244. request_id = msg["request_id"]
  245. base_dir, env = context["base_dir"], context['env']
  246. push_queue, image_queue, push_ex_queue, hb_queue = self._push_queue, self._image_queue, self._push_ex_queue, \
  247. self._hb_queue
  248. aiFilePath, logo = context["aiFilePath"], context["logo"]
  249. ai_video_file, push_p, push_url = None, None, msg["push_url"]
  250. service_timeout = int(context["service"]["timeout"]) + 120
  251. frame_score = context["service"]["filter"]["frame_score"]
  252. ex = None
  253. ex_status = True
  254. high_score_image = {}
  255. # 相似度, 默认值0.65
  256. similarity = context["service"]["filter"]["similarity"]
  257. # 图片相似度开关
  258. picture_similarity = bool(context["service"]["filter"]["picture_similarity"])
  259. frame_step = int(context["service"]["filter"]["frame_step"])
  260. try:
  261. init_log(base_dir, env)
  262. logger.info("开始启动离线推流进程!requestId:{}", request_id)
  263. with ThreadPoolExecutor(max_workers=2) as t:
  264. # 定义三种推流、写原视频流、写ai视频流策略
  265. # 第一个参数时间, 第二个参数重试次数
  266. p_push_status, ai_write_status = [0, 0], [0, 0]
  267. start_time = time()
  268. while True:
  269. # 检测推流执行超时时间
  270. if time() - start_time > service_timeout:
  271. logger.error("离线推流超时, requestId: {}", request_id)
  272. raise ServiceException(ExceptionType.TASK_EXCUTE_TIMEOUT.value[0],
  273. ExceptionType.TASK_EXCUTE_TIMEOUT.value[1])
  274. # 系统由于各种问题可能会杀死内存使用多的进程, 自己杀掉自己
  275. if psutil.Process(getpid()).ppid() == 1:
  276. logger.info("离线推流进程检测到父进程异常停止, 自动停止推流进程, requestId: {}", request_id)
  277. ex_status = False
  278. for q in [push_queue, image_queue, push_ex_queue, hb_queue]:
  279. clear_queue(q)
  280. break
  281. # 获取推流的视频帧
  282. push_r = get_no_block_queue(push_queue)
  283. if push_r is not None:
  284. # [(1, ...] 视频帧操作
  285. # [(2, 操作指令)] 指令操作
  286. if push_r[0] == 1:
  287. frame_list, frame_index_list, all_frames, draw_config, push_objs = push_r[1]
  288. # 处理每一帧图片
  289. for i, frame in enumerate(frame_list):
  290. if frame_index_list[i] % 300 == 0 and frame_index_list[i] <= all_frames:
  291. task_process = "%.2f" % (float(frame_index_list[i]) / float(all_frames))
  292. put_queue(hb_queue, {"hb_value": task_process}, timeout=2)
  293. # 复制帧用来画图
  294. copy_frame = frame.copy()
  295. # 所有问题记录字典
  296. det_xywh, thread_p = {}, []
  297. for det in push_objs[i]:
  298. code, det_result = det
  299. # 每个单独模型处理
  300. # 模型编号、100帧的所有问题, 检测目标、颜色、文字图片
  301. if len(det_result) > 0:
  302. font_config, allowedList = draw_config["font_config"], draw_config[code]["allowedList"]
  303. rainbows, label_arrays = draw_config[code]["rainbows"], draw_config[code]["label_arrays"]
  304. for qs in det_result:
  305. box, score, cls = xywh2xyxy2(qs)
  306. if cls not in allowedList or score < frame_score:
  307. continue
  308. label_array, color = label_arrays[cls], rainbows[cls]
  309. rr = t.submit(draw_painting_joint, box, copy_frame, label_array, score, color, font_config)
  310. thread_p.append(rr)
  311. if det_xywh.get(code) is None:
  312. det_xywh[code] = {}
  313. cd = det_xywh[code].get(cls)
  314. if cd is None:
  315. det_xywh[code][cls] = [[cls, box, score, label_array, color]]
  316. else:
  317. det_xywh[code][cls].append([cls, box, score, label_array, color])
  318. if logo:
  319. frame = add_water_pic(frame, logo, request_id)
  320. copy_frame = add_water_pic(copy_frame, logo, request_id)
  321. if len(thread_p) > 0:
  322. for r in thread_p:
  323. r.result()
  324. frame_merge = video_conjuncing(frame, copy_frame)
  325. # 写识别视频到本地
  326. write_ai_video_result = t.submit(write_ai_video, frame_merge, aiFilePath,
  327. ai_video_file,
  328. ai_write_status, request_id)
  329. push_stream_result = t.submit(push_video_stream, frame_merge, push_p, push_url,
  330. p_push_status, request_id)
  331. # 指定多少帧上传一次问题
  332. if high_score_image.get("current_frame") is None:
  333. high_score_image["current_frame"] = frame_index_list[i]
  334. diff_frame_num = frame_index_list[i] - high_score_image["current_frame"]
  335. # 指定帧内处理逻辑
  336. if frame_step >= diff_frame_num and high_score_image.get("code") is not None and len(det_xywh) > 0:
  337. # 所有的模型编号
  338. cache_codes = set(high_score_image["code"].keys())
  339. # 遍历当前模型
  340. for det_code, det_clss in det_xywh.items():
  341. # 如果模型编号不在缓存中
  342. if det_code not in cache_codes:
  343. high_score_image["code"][det_code] = {}
  344. for cls, cls_list in det_xywh[det_code].items():
  345. if len(cls_list) > 0:
  346. high_score_image["code"][det_code][cls] = (frame, frame_index_list[i], cls_list)
  347. else:
  348. # 如果模型编号在缓存中, 检查检测目标的情况
  349. cache_clss = set(high_score_image["code"][det_code].keys())
  350. for cls, cls_list in det_xywh[det_code].items():
  351. if len(cls_list) > 0:
  352. # 如果检测目标在缓存中
  353. if cls not in cache_clss:
  354. high_score_image["code"][det_code][cls] = (frame, frame_index_list[i], cls_list)
  355. else:
  356. if len(det_xywh[det_code][cls]) > len(high_score_image["code"][det_code][cls][2]):
  357. high_score_image["code"][det_code][cls] = (frame, frame_index_list[i], cls_list)
  358. elif len(det_xywh[det_code][cls]) == len(high_score_image["code"][det_code][cls][2]):
  359. # [cls, box, score, label_array, color]
  360. hs = 0
  361. for ii, s in enumerate(cls_list):
  362. if s[2] >= high_score_image["code"][det_code][cls][2][ii][2]:
  363. hs += 1
  364. if hs == len(cls_list):
  365. high_score_image["code"][det_code][cls] = (frame, frame_index_list[i], cls_list)
  366. if diff_frame_num > frame_step:
  367. if high_score_image.get("code") is not None:
  368. high_score_image["or_frame"] = frame
  369. put_queue(image_queue, (1, [high_score_image["code"], all_frames, draw_config["font_config"]]))
  370. high_score_image["code"] = None
  371. high_score_image["current_frame"] = None
  372. # 如果有问题, 走下面的逻辑
  373. if len(det_xywh) > 0:
  374. flag = True
  375. # 比较上一次识别到问题的帧和当前问题帧的相似度
  376. if picture_similarity and high_score_image.get("or_frame") is not None:
  377. hash1 = ImageUtils.dHash(high_score_image["or_frame"])
  378. hash2 = ImageUtils.dHash(frame)
  379. dist = ImageUtils.Hamming_distance(hash1, hash2)
  380. similarity_1 = 1 - dist * 1.0 / 64
  381. if similarity_1 >= similarity:
  382. flag = False
  383. # 如果缓存问题是空的,给缓存添加问题
  384. if flag and high_score_image.get("code") is None:
  385. high_score_image["code"] = {}
  386. for code, det_list in det_xywh.items():
  387. if high_score_image["code"].get(code) is None:
  388. high_score_image["code"][code] = {}
  389. for cls, cls_list in det_list.items():
  390. high_score_image["code"][code][cls] = (frame, frame_index_list[i], cls_list)
  391. push_p = push_stream_result.result(timeout=5)
  392. ai_video_file = write_ai_video_result.result(timeout=5)
  393. # 接收停止指令
  394. if push_r[0] == 2:
  395. if 'stop' == push_r[1]:
  396. logger.info("停止推流进程, requestId: {}", request_id)
  397. break
  398. if 'stop_ex' == push_r[1]:
  399. logger.info("停止推流进程, requestId: {}", request_id)
  400. ex_status = False
  401. break
  402. del push_r
  403. else:
  404. sleep(1)
  405. except ServiceException as s:
  406. logger.error("推流进程异常:{}, requestId:{}", s.msg, request_id)
  407. ex = s.code, s.msg
  408. except Exception:
  409. logger.error("推流进程异常:{}, requestId:{}", format_exc(), request_id)
  410. ex = ExceptionType.SERVICE_INNER_EXCEPTION.value[0], ExceptionType.SERVICE_INNER_EXCEPTION.value[1]
  411. finally:
  412. # 关闭推流管, 分析视频写对象
  413. close_all_p(push_p, None, ai_video_file, request_id)
  414. if ex:
  415. code, msg = ex
  416. put_queue(push_ex_queue, (1, code, msg), timeout=2)
  417. else:
  418. if ex_status:
  419. # 关闭推流的时候, 等待1分钟图片队列处理完,如果1分钟内没有处理完, 清空图片队列, 丢弃没有上传的图片
  420. c_time = time()
  421. while time() - c_time < 60:
  422. if image_queue.qsize() == 0 or image_queue.empty():
  423. break
  424. sleep(2)
  425. for q in [push_queue, image_queue, push_ex_queue, hb_queue]:
  426. clear_queue(q)
  427. logger.info("推流进程停止完成!requestId:{}", request_id)