Ejemplo n.º 1
0
class SmartCam:
    # 帧大小
    frame_hw = [480, 640]
    # 帧速率
    fps = 30
    # 图像质量
    jpeg_quality = 80

    need_quit = False

    # 快速变量,如果发现xx将会设定以下变量为True,没有则False
    has_move = False
    has_smoke = False
    has_fire = False
    has_human = False

    _is_start = False
    _recording_frame_hw = None

    _human_boxes = []
    draw_human_boxes = True

    def __init__(self, cam_id=0, *, debug_show=False):
        '''
        初始化函数
        :param cam_id: 当cam_id为-1时,将会使用IP摄像机,如果为其他值,例如1,将会使用本机的1号摄像头
        :param debug_show: 是否启动调试输出,为True时,将会启动debug模式,并且会显示所有子模块的处理情况的显示输出
        '''
        self.debug_show = debug_show
        print('initing cam')
        self.cam_id = cam_id
        if cam_id == -1:
            # 使用IP摄像头
            self.smart_cam = smart_cam_receiver()
        else:
            # 打开本地摄像头失败时,将会抛出异常
            self.cam = imageio.get_reader('<video%d>' % cam_id, fps=self.fps)
        print('open cam complete')

        print('load detector')
        # 载入各种检测器
        self.fire_detector = FireDetector(debug_show=debug_show)
        self.smoke_detector = SmokeDetector()
        self.diff_move_detector = DiffMoveDetector(debug_show=debug_show)
        self.human_detector = HumanDetector(const_define.yolo_net_file, const_define.yolo_weights_file, debug_show=debug_show)
        self.recoder = Recorder(const_define.record_path)
        self.notifier = Notifier(self.recoder)
        print('load detector complete')

        # 初始化第一帧为全黑图像
        self.frame = np.zeros([*self.frame_hw, 3], np.uint8)

        # 初始化各个检测器的工作线程
        self.fire_detect_thread = threading.Thread(target=self.fire_detect_run)
        self.smoke_detect_thread = threading.Thread(target=self.smoke_detect_run)
        self.human_detect_thread = threading.Thread(target=self.human_detect_run)
        self.move_detect_thread = threading.Thread(target=self.move_detect_run)

        if self.cam_id == -1:
            self.smart_cam.ctrl_framehw(self.frame_hw)
            self.smart_cam.ctrl_fps(self.fps)

    def load(self, path=const_define.main_config_path):
        '''
        载入配置函数,可以动态载入,载入配置时将会马上使用新的配置
        :param path:
        :return:
        '''
        print('loading config')
        # 载入配置时,应当暂停部分操作
        cfg = json.load(open(path, 'r'))
        self.frame_hw = cfg['frame_hw']
        self.fps = cfg['fps']
        self.jpeg_quality = cfg['jpeg_quality']
        self.fire_detector.load(cfg['FireDetector'])
        self.smoke_detector.load(cfg['SmokeDetector'])
        self.diff_move_detector.load(cfg['DiffMoveDetector'])
        self.human_detector.load(cfg['HumanDetector'])
        self.notifier.load(cfg['Notifier'])
        if self.cam_id == -1:
            self.smart_cam.load(cfg['cam'])
            self.smart_cam.ctrl_framehw(self.frame_hw)
            self.smart_cam.ctrl_fps(self.fps)
            self.smart_cam.ctrl_jpeg_quality(self.jpeg_quality)
        print('load config success')

    def save(self, path='config.json'):
        '''
        保存配置到指定文件,格式为json
        :param path: 要保存到的配置文件路径
        :return: 同时也会返回一份配置文件字典,类型为dict
        '''
        print('saving config')
        cfg = {
            'frame_hw': self.frame_hw,
            'fps': self.fps,
            'jpeg_quality': self.jpeg_quality,
            'FireDetector': self.fire_detector.save(),
            'SmokeDetector': self.smoke_detector.save(),
            'DiffMoveDetector': self.diff_move_detector.save(),
            'HumanDetector': self.human_detector.save(),
            'Notifier': self.notifier.save()
        }
        if self.cam_id == -1:
            cfg.update({'cam': self.smart_cam.save()})
        json.dump(cfg, open(path, 'w'))
        print('save config success')
        return cfg

    def fire_detect_run(self):
        '''
        火焰监测线程
        :return:
        '''
        while not self.need_quit:
            self.fire_detector.push_frame(self.frame)
            bboxes = self.fire_detector.detect()
            if len(bboxes) > 0:
                self.notifier.notice(notify_type.type_fire, 'found fire')
                self.has_fire = True
            else:
                self.has_fire = False
            time.sleep(1/self.fps)

    def smoke_detect_run(self):
        '''
        烟雾检测线程
        :return:
        '''
        if self.cam_id == -1:
            while not self.need_quit:
                b = self.smart_cam.found_smoke
                if b:
                    self.notifier.notice(notify_type.type_smoke, 'found smoke')
                    self.has_smoke = True
                else:
                    self.has_smoke = False
                time.sleep(1 / self.fps)
        else:
            self.smoke_detector.start()
            while not self.need_quit:
                b = self.smoke_detector.detect()
                if b:
                    self.notifier.notice(notify_type.type_smoke, 'found smoke')
                    self.has_smoke = True
                else:
                    self.has_smoke = False
                time.sleep(1 / self.fps)
            self.smoke_detector.stop()
            self.smoke_detector.cleanup()

    def move_detect_run(self):
        '''
        画面变动检测线程
        :return:
        '''
        while not self.need_quit:
            self.diff_move_detector.push_frame(self.frame)
            bboxes = self.diff_move_detector.detect()
            if len(bboxes) > 0:
                self.notifier.notice(notify_type.type_move, 'found move')
                self.has_move = True
            else:
                self.has_move = False
            time.sleep(1 / self.fps)

    def human_detect_run(self):
        '''
        人类检测线程
        :return:
        '''
        while not self.need_quit:
            self.human_detector.push_frame(self.frame)
            bboxes = self.human_detector.detect()
            if len(bboxes) > 0:
                self._human_boxes = bboxes
                self.notifier.notice(notify_type.type_human, 'found human')
                self.has_human = True
            else:
                self._human_boxes = []
                self.has_human = False
            time.sleep(1 / self.fps)

    # def noise_detect_run(self):
    #     while not self.need_quit:
    #         # 尚未完成异常噪音检测
    #         time.sleep(1 / self.fps)

    def cleanup(self):
        '''
        退出程序时调用,安全清理内存
        :return:
        '''
        self.need_quit = True
        self.fire_detect_thread.join()
        self.smoke_detect_thread.join()
        self.human_detect_thread.join()
        self.move_detect_thread.join()
        self.smart_cam.cleanup()
        self.notifier.cleanup()

    def run(self):
        '''
        主消息循环,需要手动调用,此循环不会退出
        :return:
        '''
        print('start watch')
        while not self.need_quit:
            # 检查摄像机id,如果是-1代表使用远程摄像头
            if self.cam_id == -1:
                # 从IP相机获得图像数据
                if self.smart_cam.offline:
                    print('camera offline')
                    time.sleep(1)
                img = self.smart_cam.get_cam_img()
                if img is not None:
                    # img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
                    self.frame = img
            else:
                # 从本地相机获取图像数据
                img = self.cam.get_next_data()
                self.frame = img

            # 第一次循环时,将会启动各个探测模块的线程,然后会设定 _is_start 标志,避免再次进入
            if not self._is_start:
                self._is_start = True
                self.fire_detect_thread.start()
                self.smoke_detect_thread.start()
                self.human_detect_thread.start()
                self.move_detect_thread.start()

            # 是否可以录像由 notifier 模块的 can_record 变量控制
            # 为 True 时代表可以开始录像,为 False 时代表应停止录像
            if self.notifier.can_record:
                # 如果录像中途切换了分辨率,关闭上一个录像后,再次启动
                if self._recording_frame_hw != self.frame_hw:
                    self.recoder.stop_video_record()
                    self._recording_frame_hw = self.frame_hw
                self.recoder.start_video_record(self.fps)
                self.recoder.next_frame(self.frame)
            else:
                self.recoder.stop_video_record()

            # 如果是debug模式,则显示相关信息
            if not self.debug_show:
                time.sleep(1 / self.fps)
            else:
                cv2.imshow('main', sc.frame)
                cv2.waitKey(1000 // self.fps)

    def cam_turn_left(self):
        '''
        每次调用此函数时,将会使IP相机的云台向左旋转0.5个单位
        如果没有连接IP相机,此函数将会无效
        :return:
        '''
        if self.cam_id == -1:
            self.smart_cam.ctrl_cam_angle('left')
        else:
            print('Unsupport normal cam')

    def cam_turn_right(self):
        '''
        每次调用此函数时,将会使IP相机的云台向右旋转0.5个单位
        如果没有连接IP相机,此函数将会无效
        :return:
        '''
        if self.cam_id == -1:
            self.smart_cam.ctrl_cam_angle('right')
        else:
            print('Unsupport normal cam')

    def get_current_img(self):
        '''
        返回当前图像,如果没有初始化成功,返回None
        :return: np.array
        '''
        return self.frame.copy()

    def get_current_jpg(self):
        '''
        返回当前图像的JPEG编码格式的字节串,如果没有初始化成功,返回None
        :return: None or bytes
        '''
        img = self.get_current_img()
        human_boxes = self._human_boxes.copy()
        if img is not None:
            if self.draw_human_boxes:
                for box in human_boxes:
                    cv2.rectangle(img, tuple(box[:2]), tuple(box[2:]), (0, 255, 0))

            img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
            _, data = cv2.imencode('.jpg', img, (cv2.IMWRITE_JPEG_QUALITY, self.jpeg_quality))
            return bytes(data)
        return None
    
    def get_recent_msg(self):
        return list(self.notifier.get_recent_msg())