You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

296 lines
9.6 KiB

# Copyright (c) OpenMMLab. All rights reserved.
import logging
import time
from queue import Full, Queue
from threading import Thread
from typing import List, Optional, Union
import cv2
import numpy as np
from mmcv import color_val
from mmpose.utils.timer import RunningAverage
from .builder import NODES
from .node import Node
try:
import psutil
psutil_proc = psutil.Process()
except (ImportError, ModuleNotFoundError):
psutil_proc = None
@NODES.register_module()
class ModelResultBindingNode(Node):
def __init__(self, name: str, frame_buffer: str, result_buffer: str,
output_buffer: Union[str, List[str]]):
super().__init__(name=name, enable=True)
self.synchronous = None
# Cache the latest model result
self.last_result_msg = None
self.last_output_msg = None
# Inference speed analysis
self.frame_fps = RunningAverage(window=10)
self.frame_lag = RunningAverage(window=10)
self.result_fps = RunningAverage(window=10)
self.result_lag = RunningAverage(window=10)
# Register buffers
# Note that essential buffers will be set in set_runner() because
# it depends on the runner.synchronous attribute.
self.register_input_buffer(result_buffer, 'result', essential=False)
self.register_input_buffer(frame_buffer, 'frame', essential=False)
self.register_output_buffer(output_buffer)
def set_runner(self, runner):
super().set_runner(runner)
# Set synchronous according to the runner
if runner.synchronous:
self.synchronous = True
essential_input = 'result'
else:
self.synchronous = False
essential_input = 'frame'
# Set essential input buffer according to the synchronous setting
for buffer_info in self._input_buffers:
if buffer_info.input_name == essential_input:
buffer_info.essential = True
def process(self, input_msgs):
result_msg = input_msgs['result']
# Update last result
if result_msg is not None:
# Update result FPS
if self.last_result_msg is not None:
self.result_fps.update(
1.0 /
(result_msg.timestamp - self.last_result_msg.timestamp))
# Update inference latency
self.result_lag.update(time.time() - result_msg.timestamp)
# Update last inference result
self.last_result_msg = result_msg
if not self.synchronous:
# Asynchronous mode: Bind the latest result with the current frame.
frame_msg = input_msgs['frame']
self.frame_lag.update(time.time() - frame_msg.timestamp)
# Bind result to frame
if self.last_result_msg is not None:
frame_msg.set_full_results(
self.last_result_msg.get_full_results())
frame_msg.merge_route_info(
self.last_result_msg.get_route_info())
output_msg = frame_msg
else:
# Synchronous mode: Directly output the frame that the model result
# was obtained from.
self.frame_lag.update(time.time() - result_msg.timestamp)
output_msg = result_msg
# Update frame fps and lag
if self.last_output_msg is not None:
self.frame_lag.update(time.time() - output_msg.timestamp)
self.frame_fps.update(
1.0 / (output_msg.timestamp - self.last_output_msg.timestamp))
self.last_output_msg = output_msg
return output_msg
def _get_node_info(self):
info = super()._get_node_info()
info['result_fps'] = self.result_fps.average()
info['result_lag (ms)'] = self.result_lag.average() * 1000
info['frame_fps'] = self.frame_fps.average()
info['frame_lag (ms)'] = self.frame_lag.average() * 1000
return info
@NODES.register_module()
class MonitorNode(Node):
_default_ignore_items = ['timestamp']
def __init__(self,
name: str,
frame_buffer: str,
output_buffer: Union[str, List[str]],
enable_key: Optional[Union[str, int]] = None,
enable: bool = False,
x_offset=20,
y_offset=20,
y_delta=15,
text_color='black',
background_color=(255, 183, 0),
text_scale=0.4,
ignore_items: Optional[List[str]] = None):
super().__init__(name=name, enable_key=enable_key, enable=enable)
self.x_offset = x_offset
self.y_offset = y_offset
self.y_delta = y_delta
self.text_color = color_val(text_color)
self.background_color = color_val(background_color)
self.text_scale = text_scale
if ignore_items is None:
self.ignore_items = self._default_ignore_items
else:
self.ignore_items = ignore_items
self.register_input_buffer(frame_buffer, 'frame', essential=True)
self.register_output_buffer(output_buffer)
def process(self, input_msgs):
frame_msg = input_msgs['frame']
frame_msg.update_route_info(
node_name='System Info',
node_type='dummy',
info=self._get_system_info())
img = frame_msg.get_image()
route_info = frame_msg.get_route_info()
img = self._show_route_info(img, route_info)
frame_msg.set_image(img)
return frame_msg
def _get_system_info(self):
sys_info = {}
if psutil_proc is not None:
sys_info['CPU(%)'] = psutil_proc.cpu_percent()
sys_info['Memory(%)'] = psutil_proc.memory_percent()
return sys_info
def _show_route_info(self, img, route_info):
canvas = np.full(img.shape, self.background_color, dtype=img.dtype)
x = self.x_offset
y = self.y_offset
max_len = 0
def _put_line(line=''):
nonlocal y, max_len
cv2.putText(canvas, line, (x, y), cv2.FONT_HERSHEY_DUPLEX,
self.text_scale, self.text_color, 1)
y += self.y_delta
max_len = max(max_len, len(line))
for node_info in route_info:
title = f'{node_info["node"]}({node_info["node_type"]})'
_put_line(title)
for k, v in node_info['info'].items():
if k in self.ignore_items:
continue
if isinstance(v, float):
v = f'{v:.1f}'
_put_line(f' {k}: {v}')
x1 = max(0, self.x_offset)
x2 = min(img.shape[1], int(x + max_len * self.text_scale * 20))
y1 = max(0, self.y_offset - self.y_delta)
y2 = min(img.shape[0], y)
src1 = canvas[y1:y2, x1:x2]
src2 = img[y1:y2, x1:x2]
img[y1:y2, x1:x2] = cv2.addWeighted(src1, 0.5, src2, 0.5, 0)
return img
def bypass(self, input_msgs):
return input_msgs['frame']
@NODES.register_module()
class RecorderNode(Node):
"""Record the frames into a local file."""
def __init__(
self,
name: str,
frame_buffer: str,
output_buffer: Union[str, List[str]],
out_video_file: str,
out_video_fps: int = 30,
out_video_codec: str = 'mp4v',
buffer_size: int = 30,
):
super().__init__(name=name, enable_key=None, enable=True)
self.queue = Queue(maxsize=buffer_size)
self.out_video_file = out_video_file
self.out_video_fps = out_video_fps
self.out_video_codec = out_video_codec
self.vwriter = None
# Register buffers
self.register_input_buffer(frame_buffer, 'frame', essential=True)
self.register_output_buffer(output_buffer)
# Start a new thread to write frame
self.t_record = Thread(target=self._record, args=(), daemon=True)
self.t_record.start()
def process(self, input_msgs):
frame_msg = input_msgs['frame']
img = frame_msg.get_image() if frame_msg is not None else None
img_queued = False
while not img_queued:
try:
self.queue.put(img, timeout=1)
img_queued = True
logging.info(f'{self.name}: recorder received one frame!')
except Full:
logging.info(f'{self.name}: recorder jamed!')
return frame_msg
def _record(self):
while True:
img = self.queue.get()
if img is None:
break
if self.vwriter is None:
fourcc = cv2.VideoWriter_fourcc(*self.out_video_codec)
fps = self.out_video_fps
frame_size = (img.shape[1], img.shape[0])
self.vwriter = cv2.VideoWriter(self.out_video_file, fourcc,
fps, frame_size)
assert self.vwriter.isOpened()
self.vwriter.write(img)
logging.info('Video recorder released!')
if self.vwriter is not None:
self.vwriter.release()
def on_exit(self):
try:
# Try putting a None into the output queue so the self.vwriter will
# be released after all queue frames have been written to file.
self.queue.put(None, timeout=1)
self.t_record.join(timeout=1)
except Full:
pass
if self.t_record.is_alive():
# Force to release self.vwriter
logging.info('Video recorder forced release!')
if self.vwriter is not None:
self.vwriter.release()